Compare commits

...

28 Commits

Author SHA1 Message Date
58ab306428 feat(ekonomi): implement UMKM module with CRUD API and Dashboard analytics 2026-04-20 16:51:59 +08:00
97902f6277 bump: version 0.1.10 -> 0.1.11
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 15:18:58 +08:00
ef7d1752de fix(docker): remove incorrect _seeder_list path, seeders are in prisma/ already
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 15:18:49 +08:00
f9de4b7a35 bump: version 0.1.9 -> 0.1.10
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 15:03:01 +08:00
13873c9fe7 fix(docker): copy only seed-related files to runner stage
Instead of full src/, copy only prisma/, src/lib/, src/_seeder_list/
and tsconfig.json for seed script support. Reduces image size.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 15:02:54 +08:00
03b084d9d4 bump: version 0.1.8 -> 0.1.9 - fix tsconfig.json in docker image
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 14:31:11 +08:00
5df9698599 fix(docker): copy tsconfig.json to runner stage for prisma seed support
Seed script uses @/* path aliases which require tsconfig.json.
Without it, 'bunx prisma db seed' fails with 'no such file or directory'.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 14:30:52 +08:00
3d3e5ffc87 docs: update deployment workflow instructions in QWEN.md
Add detailed deployment workflow:
- Version bump before deploy
- Trigger publish.yml with ref=main, stack_env=stg, tag=<version>
- Wait for publish to complete
- Trigger re-pull.yml with ref=main, stack_name=desa-darmasaba, stack_env=stg
- Include GitHub CLI commands for reference

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 12:08:21 +08:00
e80e333eed fix(migration): move JenisMigrasi enum creation before MigrasiPenduduk table
Enum must be created before it's used in CREATE TABLE.
This fixes P3018 error on staging where JenisMigrasi type does not exist.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:58:08 +08:00
b1289831f3 Revert "fix(deployment): handle failed migrations automatically in docker-entrypoint"
This reverts commit 3c4e273e26.
2026-04-14 11:53:35 +08:00
3c4e273e26 fix(deployment): handle failed migrations automatically in docker-entrypoint
- Detect failed migrations in _prisma_migrations table
- Auto resolve with 'prisma migrate resolve --rolled-back'
- Fallback to 'prisma db push' if resolve fails
- Ensures staging/prod can deploy even with migration history issues

Fixes P3009 error on staging where 20260406032433_init migration failed

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:26:49 +08:00
de4563c914 fix: fileStorage path issues between local and staging environments
- Store relative paths in database instead of absolute paths
- Reconstruct absolute paths at runtime using UPLOAD_DIR env var
- Add VOLUME for /app/uploads in Dockerfile for persistent storage
- Create uploads directory with proper permissions in Docker
- Add error handling for missing UPLOAD_DIR in findUniq.ts
- Simplify GitHub workflow memory in QWEN.md (manual handling)

This fixes the 500 errors on staging for file create/delete operations
caused by environment-specific absolute paths stored in database.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:03:11 +08:00
11ff5f5c01 bump: version 0.1.7 -> 0.1.8 - add kependudukan migration
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:03:11 +08:00
ed44222594 fix(database): add migration for kependudukan tables
- Add DataBanjar, DistribusiAgama, DistribusiUmur, MigrasiPenduduk, DinamikaPenduduk tables
- Add indexes for performance (tahun, isActive)
- Add JenisMigrasi enum (MASUK, KELUAR)
- Fixes: Error 500 on all CRUD kependudukan endpoints in staging

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:03:11 +08:00
fd7579d6d3 bump: version 0.1.6 -> 0.1.7 - auto migration on startup
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:03:11 +08:00
e7c3c020c2 fix(deployment): add auto database migration on container startup
- Create docker-entrypoint.sh to run prisma migrate deploy before app start
- Update Dockerfile to use entrypoint script
- Ensures database schema is always up-to-date after deployment
- Fixes: CRUD kependudukan error 500 di staging karena tabel belum dibuat

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:03:11 +08:00
6873e84848 bump: version 0.1.5 -> 0.1.6 - fix migrasi penduduk schema mismatch
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:03:11 +08:00
74dc9e5c18 docs: update QWEN.md
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:03:11 +08:00
04001c905b fix(kependudukan): remove jenisKelamin field and align MigrasiPenduduk with database schema
- Remove jenisKelamin field from API, state, and UI components
- Fix MigrasiPenduduk API to use null instead of undefined for optional fields
- Update create/edit forms to properly handle asal/tujuan fields based on jenis
- Fix DatePickerInput type handling with valueFormat prop
- Update list page to display asal or tujuan conditionally
- Add proper select statements in API responses
- Fix TypeScript type errors in migrasi-penduduk module

Closes: Schema mismatch causing errors when inputting migrasi penduduk data

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 11:03:11 +08:00
62a9a49502 Merge pull request #21 from bipprojectbali/tasks/kependudukan/fix-typescript-types-and-cleanup-20260413-1500
refactor(kependudukan): improve TypeScript types and clean up code
2026-04-13 15:01:39 +08:00
80186bf493 refactor(kependudukan): improve TypeScript types and clean up code
- Add proper TypeScript interfaces for seeder files
- Rename MigrasiPendudukForm interface for consistency
- Separate asal/tujuan fields in MigrasiPenduduk API based on jenis
- Remove unnecessary eslint-disable comments
- Add local type definitions for public kependudukan pages
- Clean up unused imports (React, Flex, IconBuilding)
- Improve type safety in form handlers (handleChangeText vs handleChangeSelect)
- Add explicit type casting where needed to fix type errors

Co-authored-by: Qwen Code

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 15:00:33 +08:00
e669dcee25 Merge pull request #20 from bipprojectbali/tasks/kependudukan/add-seeders-api-year-filter-navbar-menu-20260410-1430
Tasks/kependudukan/add seeders api year filter navbar menu 20260410 1430
2026-04-13 11:09:09 +08:00
d84edc44f5 fix: rename DistribusiUmur.kelompok to rentangUmur to match UI/API
- Update Prisma schema: kelompok -> rentangUmur
- Update seed data JSON: kelompok -> rentangUmur
- Update seeder file: use rentangUmur field
- This fixes the empty data issue in distribusi-umur admin page

Note: Run 'bunx prisma db push' to apply schema migration

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-10 12:03:25 +08:00
8b14c6ce44 feat: add kependudukan seeders, API routes, year filter, and navbar menu
- Add Prisma models: DataBanjar, DistribusiAgama, DistribusiUmur, MigrasiPenduduk, DinamikaPenduduk
- Create seeders for all kependudukan models with year 2026 data
- Register Kependudukan API routes in route.ts
- Update API findMany endpoints to make tahun parameter optional
- Add YearFilter reusable component for admin pages
- Update 4 kependudukan admin pages with year filter UI
- Fix Mantine color array in AdminThemeProvider (add 10th element)
- Fix invalid Mantine color scale in paguTable.tsx (gray.50 -> gray.1)
- Add Kependudukan menu to navbar-list-menu.ts
- Fix Bun JSON import resolution with loadJsonData helper
- Update 74 seeder files to use dynamic JSON loading

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-10 11:54:36 +08:00
5e822f0b05 feat: implement Kependudukan menu with CRUD admin pages
- Add Distribusi Umur admin pages (list, create, edit)
- Add Data Banjar admin pages (list, create, edit)
- Add Migrasi Penduduk admin pages (list, create, edit)
- Update state management with full CRUD operations for all modules
- Add Kependudukan menu to admin sidebar (devBar, navBar, role1)
- Add public pages for Distribusi Umur with age range sorting
- Update Dinamika Penduduk to use real-time birth/death data
- Add Biome configuration for code linting
- Create API routes for all Kependudukan modules

Features:
- Pagination and search for all admin list pages
- Responsive design (table for desktop, cards for mobile)
- Delete confirmation modal
- Toast notifications for user feedback
- Zod validation for all forms
- Age range auto-sorting in public Distribusi Umur chart

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-09 17:10:29 +08:00
34a37dc63b chore(dev-dependency): add playwright-mcp package for testing
- Add playwright-mcp v0.0.19 as dev dependency
- Update bun.lock with new dependency tree
- Add .qwen configuration files

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-09 11:33:19 +08:00
0e6f7e1769 fix(landing-page): remove incorrect national holiday entry for April 9, 2026
- Remove '2026-04-09' from national holidays list as it's not an actual holiday
- Fix office status showing 'Tutup' and 'Tidak Beroperasi' incorrectly on regular workday
- Office now correctly shows 'Buka' status during working hours (07:30-15:30 on Thursday)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-09 10:49:10 +08:00
feb853d06e fix(auth): improve OTP error handling and add health check endpoint
Option 2 - Improve Error Handling:
- Track WA success status and error messages in login route
- Return debug info (including OTP code) only in non-production
- Show descriptive message when WhatsApp fails to send
- Better error categorization (HTTP error vs logic error vs connection)

Option 3 - Health Check Endpoint:
- Create /api/health/otp endpoint for OTP service diagnostics
- Support test mode with query params: ?test=true&number=6281234567890
- Check token configuration (configured vs placeholder)
- Measure response time and validate service response
- Return comprehensive status for debugging OTP issues

Usage:
- Basic check: GET /api/health/otp
- Test send: GET /api/health/otp?test=true&number=6281234567890

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-06 16:27:29 +08:00
232 changed files with 12500 additions and 188 deletions

3
.gitignore vendored
View File

@@ -55,3 +55,6 @@ next-env.d.ts
*.tar.gz
# local scripts
ai.sh

13
.qwen/settings.json Normal file
View 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
View File

@@ -0,0 +1,9 @@
{
"mcpServers": {
"playwright-mcp": {
"command": "npx",
"args": ["-y", "playwright-mcp@latest"],
"timeout": 60000
}
}
}

116
CLAUDE.md Normal file
View File

@@ -0,0 +1,116 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Desa Darmasaba is a full-stack digital village management platform for a village in Badung, Bali. It serves both a public-facing website (`/darmasaba/*`) and an admin CMS (`/admin/*`).
## Commands
```bash
# Development
bun run dev # Start dev server (port 3000)
bun run build # Production build
bun run tsc --noEmit # Type-check only
# Testing
bun run test # All tests
bun run test:api # Unit tests (Vitest)
bun run test:e2e # E2E tests (Playwright)
# Database
bunx prisma migrate deploy # Apply migrations
bunx prisma migrate dev --name <name> # Create migration
bun run prisma/seed.ts # Seed database
bunx prisma studio # Interactive DB viewer
# Linting
bun eslint . --fix
```
## Architecture
### Tech Stack
- **Framework**: Next.js 15 (App Router) + React 19
- **Runtime/Package manager**: Bun (not npm)
- **API server**: Elysia.js (mounted at `/api/[[...slugs]]`)
- **ORM**: Prisma + PostgreSQL
- **UI**: Mantine UI v7-8
- **State**: Jotai (atoms), Valtio (proxies), SWR (data fetching)
- **Auth**: iron-session + JWT
- **File storage**: Local uploads + Seafile (self-hosted)
### Request Flow
```
Browser → Next.js middleware (src/middleware.ts)
→ Public pages: src/app/darmasaba/
→ Admin pages: src/app/admin/
→ API: src/app/api/[[...slugs]]/route.ts (Elysia.js)
└── _lib/*.ts (domain modules)
```
The Elysia server is a single entry point with domain-specific modules: `desa.ts`, `kesehatan.ts`, `ekonomi.ts`, `keamanan.ts`, `lingkungan.ts`, `pendidikan.ts`, `kependudukan.ts`, `ppid.ts`, `inovasi.ts`, `auth/`, `user/`, `fileStorage/`. Swagger docs are auto-generated at `/api/docs`.
### Domain Modules
Each domain (desa, kesehatan, ekonomi, etc.) has:
- API handler in `src/app/api/[[...slugs]]/_lib/<domain>.ts`
- Admin CMS pages in `src/app/admin/(dashboard)/<domain>/`
- Public pages in `src/app/darmasaba/(pages)/<domain>/`
### Database (Prisma)
- Schema at `prisma/schema.prisma` (~2400 lines, 100+ models)
- Common model conventions: `@default(cuid())` IDs, `createdAt`/`updatedAt` timestamps, `deletedAt DateTime?` (soft delete), `isActive Boolean @default(true)`
- Seeders per-module in `prisma/_seeder_list/`, orchestrated by `prisma/seed.ts`
### Authentication Flow
1. User submits phone → OTP sent (email/SMS)
2. OTP validated → JWT created + iron-session stored
3. `UserSession` model tracks active sessions
4. `src/middleware.ts` validates on each request
5. `src/lib/api-auth.ts` handles JWT/session checks in API routes
### File Handling
All uploaded files reference the `FileStorage` Prisma model. Uploads land in `WIBU_UPLOAD_DIR` (default: `uploads/`). Seafile is the external storage fallback.
## Key Files
| File | Purpose |
|------|---------|
| `src/middleware.ts` | Route guards and auth |
| `src/lib/prisma.ts` | Prisma client singleton |
| `src/lib/api-auth.ts` | JWT/session validation |
| `src/lib/api-fetch.ts` | Typed fetch wrapper used by frontend |
| `src/lib/session.ts` | iron-session config |
| `next.config.ts` | Next.js config (cache headers, allowed origins) |
| `postcss.config.cjs` | Mantine CSS preset and breakpoints |
| `docker-entrypoint.sh` | Runs `prisma migrate deploy` then starts app |
## Environment Variables
Copy `.env.example` to `.env`. Required variables:
```env
DATABASE_URL="postgresql://..."
NEXT_PUBLIC_BASE_URL="/"
BASE_SESSION_KEY="..." # random string
BASE_TOKEN_KEY="..." # random string
SESSION_PASSWORD="..." # min 32 chars
SEAFILE_TOKEN="..."
SEAFILE_REPO_ID="..."
SEAFILE_URL="..."
```
## Docker
Multi-stage build: `oven/bun:1-debian` → builder → runner. The runner creates a `nextjs` user (UID 1001), exposes port 3000, and mounts `/app/uploads` as a volume. Entrypoint runs migrations automatically.
## CI/CD
GitHub Actions workflows in `.github/workflows/`:
- `docker-publish.yml` — triggers on `v*` tags, pushes to GHCR
- `publish.yml` — manual build & push
- `re-pull.yml` — triggers Portainer to redeploy latest image
To release: tag with `git tag -a v0.1.x -m "..."` and push the tag.

View File

@@ -58,10 +58,19 @@ 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/src/lib ./src/lib
COPY --from=builder --chown=nextjs:nodejs /app/tsconfig.json ./tsconfig.json
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 ["bun", "start"]
CMD ["/app/docker-entrypoint.sh"]

24
MIND/PLAN/umkm-module.md Normal file
View File

@@ -0,0 +1,24 @@
# Plan: UMKM Module Implementation
## Goal
Implement UMKM, ProdukUmkm, and PenjualanProduk module with CRUD API and Dashboard analytics.
## Steps
1. Update Prisma Schema (already done in file).
2. Run database migration and seed data.
3. Implement UMKM CRUD API.
4. Implement ProdukUmkm CRUD API.
5. Implement PenjualanProduk CRUD API.
6. Implement Dashboard API (KPI, Summary, Top Produk, Detail Penjualan).
7. Register all routers in the ekonomi module.
8. Verify with type check and build.
## Progress
- [x] Step 1: Update Prisma Schema
- [x] Step 2: Run database migration
- [x] Step 3: Implement UMKM CRUD API
- [x] Step 4: Implement ProdukUmkm CRUD API
- [x] Step 5: Implement PenjualanProduk CRUD API
- [x] Step 6: Implement Dashboard API
- [x] Step 7: Register routers
- [x] Step 8: Verify changes

View File

@@ -0,0 +1,28 @@
# Summary: UMKM Module Implementation
## Accomplishments
- Successfully migrated the database to include `Umkm`, `ProdukUmkm`, and `PenjualanProduk` tables.
- Implemented a complete set of CRUD API endpoints for UMKM, Products, and Sales.
- Developed a comprehensive Dashboard API providing KPIs, sales summaries, top products, and detailed stock analytics.
- Integrated the new module into the existing `ekonomi` router.
- Verified the implementation with `tsc` to ensure type safety.
## Files Created/Modified
### Modified
- `prisma/schema.prisma`: Added relations and models.
- `src/app/api/[[...slugs]]/_lib/ekonomi/index.ts`: Registered new routers.
### Created
- `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/`: CRUD for UMKM.
- `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/`: CRUD for Products.
- `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/penjualan/`: CRUD for Sales with stock management.
- `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/`: Analytics endpoints.
## Stock Management Logic
- Creating a sale decrements product stock.
- Updating a sale adjusts stock based on the difference in quantity.
- Deleting a sale increments stock back.
## Next Steps
- Implement frontend UI for the UMKM module.
- Add more comprehensive tests for the stock management logic.

34
QWEN.md
View File

@@ -229,4 +229,36 @@ Common issues and solutions:
3. Test database changes with `bunx prisma db push`
4. Use the integrated Swagger docs at `/api/docs` for API testing
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.
- **Deployment Workflow Sistematis**:
1. **Version Bump** - Update `version` di `package.json` sebelum deploy (ikuti semver: major.minor.patch)
2. **Commit** - Commit perubahan + version bump dengan pesan yang jelas
3. **Push ke Branch** - Push ke branch target (biasanya `stg` untuk staging atau `prod` untuk production)
4. **Trigger publish.yml** - Gunakan GitHub API dengan: `ref: main`, `stack_env: stg`, `tag: <versi-dari-package.json>`
5. **Tunggu publish selesai** - Workflow harus completed baru lanjut ke re-pull
6. **Trigger re-pull.yml** - Gunakan GitHub API dengan: `ref: main`, `stack_name: desa-darmasaba`, `stack_env: stg`
Branch deployment: `stg` (staging) atau `prod` (production)
Version format di package.json: `"version": "major.minor.patch"`
- **Auto Deploy After Push to deploy/stg**: Setelah push ke `deploy/stg`, otomatis trigger deployment workflow:
1. Bump version di `package.json` (increment patch)
2. Commit version bump
3. Trigger `publish.yml` via GitHub API: `ref=main`, `stack_env=stg`, `tag=<version>`
4. Polling sampai publish workflow completed
5. Trigger `re-pull.yml` via GitHub API: `ref=main`, `stack_name=desa-darmasaba`, `stack_env=stg`
Cara trigger workflow via GitHub CLI:
```bash
# Trigger publish
gh workflow run publish.yml --ref main -f stack_env=stg -f tag=<version>
# Cek status (polling)
gh run watch <run_id>
# Trigger re-pull
gh workflow run re-pull.yml --ref main -f stack_name=desa-darmasaba -f stack_env=stg
```

678
STRUKTUR-PROJEK.md Normal file
View File

@@ -0,0 +1,678 @@
# Dokumentasi Struktur Proyek - Desa Darmasaba
## 1. Ringkasan Proyek
**Desa Darmasaba** adalah aplikasi web komprehensif untuk layanan pemerintahan desa di Desa Darmasaba, Kabupaten Badung, Bali. Aplikasi ini berfungsi sebagai platform digital untuk layanan pemerintah, informasi publik, dan keterlibatan masyarakat.
### Tech Stack
| Kategori | Teknologi |
|----------|-----------|
| **Framework Frontend** | Next.js 15 dengan App Router |
| **Bahasa** | TypeScript (strict mode) |
| **Styling** | Mantine UI v7/v8 + Custom CSS |
| **Backend API** | Elysia.js (high-performance TypeScript framework) |
| **Database** | PostgreSQL |
| **ORM** | Prisma 6.3.1 |
| **Runtime** | Bun |
| **State Management** | Jotai + Valtio + SWR |
| **Autentikasi** | iron-session + JWT |
| **File Storage** | Seafile |
| **Rich Text Editor** | TipTap |
| **Charts** | Recharts + Chart.js |
| **Maps** | Leaflet + react-leaflet |
| **UI Components** | Mantine, PrimeReact, Framer Motion |
| **Validasi** | Zod |
| **Testing** | Vitest (unit), Playwright (E2E) |
| **Deployment** | Docker + GitHub Actions + Portainer |
| **Registry** | GitHub Container Registry (GHCR) |
---
## 2. Struktur Direktori
```
desa-darmasaba/
├── .github/workflows/ # GitHub Actions CI/CD
│ ├── docker-publish.yml # Auto build & push saat tag v*
│ ├── publish.yml # Manual build & push ke GHCR
│ ├── re-pull.yml # Manual re-pull image di Portainer
│ └── script/ # Script deployment
├── prisma/
│ ├── schema.prisma # Database schema (2413 baris, 100+ model)
│ ├── seed.ts # Database seeder utama
│ └── _seeder_list/ # Data seed per modul
│ ├── desa/ # Seed berita, gallery, layanan, dll
│ ├── ekonomi/ # Seed APBDes, demografi, dll
│ ├── inovasi/ # Seed ide inovatif, desa digital
│ ├── keamanan/ # Seed keamanan, kontak darurat
│ ├── kesehatan/ # Seed fasilitas kesehatan, posyandu
│ ├── kependudukan/ # Seed data penduduk
│ ├── lingkungan/ # Seed lingkungan desa
│ ├── pendidikan/ # Seed sekolah, beasiswa
│ ├── ppid/ # Seed PPID
│ └── landing-page/ # Seed landing page
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── _com/ # Komponen global (SplashScreen, WebVitals)
│ │ ├── admin/ # Panel administrasi (protected)
│ │ │ ├── _com/ # Komponen admin shared
│ │ │ ├── (dashboard)/ # Dashboard admin dengan route groups
│ │ │ │ ├── _com/ # Komponen dashboard shared
│ │ │ │ ├── _state/ # State khusus dashboard
│ │ │ │ ├── _utils/ # Utilitas dashboard
│ │ │ │ ├── auth/ # Autentikasi admin
│ │ │ │ ├── desa/ # Admin: berita, gallery, profil, layanan
│ │ │ │ ├── ekonomi/ # Admin: APBDes, demografi, BUMDes
│ │ │ │ ├── inovasi/ # Admin: ide inovatif, desa digital
│ │ │ │ ├── keamanan/ # Admin: keamanan, kontak darurat
│ │ │ │ ├── kependudukan/# Admin: banjar, agama, umur, migrasi
│ │ │ │ ├── kesehatan/ # Admin: puskesmas, posyandu, wabah
│ │ │ │ ├── landing-page/# Admin: konten landing page
│ │ │ │ ├── lingkungan/ # Admin: konservasi, sampah, penghijauan
│ │ │ │ ├── musik/ # Admin: musik desa
│ │ │ │ ├── pendidikan/ # Admin: sekolah, beasiswa, perpustakaan
│ │ │ │ ├── ppid/ # Admin: PPID, IKM, permohonan
│ │ │ │ └── user&role/ # Admin: manajemen user & role
│ │ │ ├── auth/ # Halaman login admin
│ │ │ ├── csv/ # Upload/demo CSV
│ │ │ ├── images/ # Manajemen gambar
│ │ │ └── upload-demo/ # Demo upload
│ │ │
│ │ ├── api/ # API routes (Elysia.js)
│ │ │ ├── [[...slugs]]/ # Catch-all route untuk Elysia
│ │ │ │ ├── _lib/ # Modul API per domain
│ │ │ │ │ ├── auth/ # Autentikasi API
│ │ │ │ │ ├── desa/ # API modul desa
│ │ │ │ │ ├── ekonomi/ # API modul ekonomi
│ │ │ │ │ ├── fileStorage/ # API file storage
│ │ │ │ │ ├── inovasi/ # API modul inovasi
│ │ │ │ │ ├── keamanan/# API modul keamanan
│ │ │ │ │ ├── kependudukan/ # API modul kependudukan
│ │ │ │ │ ├── kesehatan/ # API modul kesehatan
│ │ │ │ │ ├── landing_page/ # API landing page
│ │ │ │ │ ├── lingkungan/ # API modul lingkungan
│ │ │ │ │ ├── pendidikan/ # API modul pendidikan
│ │ │ │ │ ├── ppid/ # API modul PPID
│ │ │ │ │ ├── search/ # API pencarian global
│ │ │ │ │ └── user/ # API user management
│ │ │ │ └── route.ts # Entry point Elysia server
│ │ │ ├── admin/ # API khusus admin
│ │ │ ├── auth/ # API autentikasi
│ │ │ ├── health/ # Health check endpoint
│ │ │ ├── layout/ # API layout
│ │ │ ├── news/ # API berita
│ │ │ ├── subscribe/ # API subscription (email)
│ │ │ └── tts/ # Text-to-Speech (ElevenLabs)
│ │ │
│ │ ├── context/ # React contexts
│ │ │ └── MusicContext.tsx # Context untuk pemutar musik
│ │ │
│ │ ├── darmasaba/ # Halaman publik (front-facing)
│ │ │ ├── _com/ # Komponen shared publik
│ │ │ │ ├── main-page/ # Komponen halaman utama
│ │ │ │ ├── Navbar.tsx # Navigasi utama
│ │ │ │ ├── Footer.tsx # Footer
│ │ │ │ ├── FixedPlayerBar.tsx # Music player bar
│ │ │ │ ├── LoadDataFirstClient.tsx # Data prefetching
│ │ │ │ ├── NewsReader.tsx # Component pembaca berita
│ │ │ │ ├── globalSearch.tsx # Pencarian global
│ │ │ │ └── scrollToTopButton.tsx
│ │ │ ├── (pages)/ # Halaman publik utama
│ │ │ │ ├── desa/ # Halaman: profil, berita, gallery, layanan
│ │ │ │ ├── ekonomi/ # Halaman: APBDes, BUMDes, demografi
│ │ │ │ ├── inovasi/ # Halaman: inovasi desa
│ │ │ │ ├── keamanan/ # Halaman: keamanan lingkungan
│ │ │ │ ├── kependudukan/# Halaman: data penduduk
│ │ │ │ ├── kesehatan/ # Halaman: fasilitas kesehatan
│ │ │ │ ├── lingkungan/ # Halaman: lingkungan desa
│ │ │ │ ├── module/ # Halaman modul tambahan
│ │ │ │ ├── musik/ # Halaman: musik desa
│ │ │ │ ├── pendidikan/ # Halaman: pendidikan
│ │ │ │ └── ppid/ # Halaman: PPID publik
│ │ │ ├── (tambahan)/ # Halaman tambahan
│ │ │ ├── layout.tsx # Layout utama publik
│ │ │ └── page.tsx # Landing page utama
│ │ │
│ │ ├── login/ # Halaman login
│ │ ├── registrasi/ # Halaman registrasi
│ │ ├── waiting-room/ # Halaman waiting room
│ │ ├── terms-of-service/ # Halaman syarat layanan
│ │ ├── test-upload/ # Halaman tes upload
│ │ ├── validasi/ # Halaman validasi
│ │ ├── coba/ # Halaman percobaan
│ │ ├── percobaan/ # Halaman percobaan lainnya
│ │ ├── layout.tsx # Root layout (MantineProvider)
│ │ ├── page.tsx # Root page
│ │ ├── error.tsx # Error boundary
│ │ ├── not-found.tsx # 404 page
│ │ ├── globals.css # Global styles
│ │ └── favicon.ico
│ │
│ ├── components/
│ │ └── admin/ # Komponen admin reusable
│ │ ├── AdminThemeProvider.tsx
│ │ ├── DarkModeToggle.tsx
│ │ ├── UnifiedSurface.tsx
│ │ └── UnifiedTypography.tsx
│ │
│ ├── con/ # Constants & konfigurasi
│ │ └── colors.ts # Palet warna
│ │
│ ├── lib/ # Utility functions
│ │ ├── router/ # Router utilities
│ │ ├── api-auth.ts # Autentikasi API
│ │ ├── api-fetch.ts # Helper fetch API
│ │ ├── EnvStringParse.ts # Parser environment variables
│ │ ├── prisma.ts # Prisma client instance
│ │ ├── seafile-auth-service.ts # Integrasi Seafile
│ │ └── session.ts # iron-session helper
│ │
│ ├── middlewares/ # Next.js middleware
│ ├── state/ # Global state (Jotai/Valtio)
│ │ ├── darkModeStore.ts # State dark mode
│ │ ├── state-layanan.ts # State layanan
│ │ ├── state-list-image.ts # State daftar gambar
│ │ └── state-nav.ts # State navigasi
│ │
│ ├── store/ # State management tambahan
│ └── types/ # TypeScript type definitions
├── public/ # Static assets
│ └── assets/ # Gambar, icon, dll
├── uploads/ # Directory upload (runtime)
│ └── image/ # Upload gambar
├── .env.example # Contoh environment variables
├── .gitignore
├── AGENTS.md # Panduan untuk AI coding agents
├── Dockerfile # Docker image definition
├── docker-entrypoint.sh # Entry point container
├── next.config.ts # Next.js configuration
├── package.json # Dependencies & scripts
├── tsconfig.json # TypeScript configuration
├── biome.json # Biome linter config
├── eslint.config.mjs # ESLint config
├── NOTE.md # Catatan deployment
└── QWEN.md # Konteks & memori proyek
```
---
## 3. Arsitektur Aplikasi
### 3.1 Arsitektur Keseluruhan
```
┌─────────────────────────────────────────────────────────┐
│ Client (Browser) │
└────────────┬────────────────────────────┬────────────────┘
│ │
│ Next.js Pages │ API Calls
│ (SSR/CSR) │
▼ ▼
┌────────────────────────┐ ┌────────────────────────────┐
│ Next.js 15 App Router│ │ Elysia.js API Server │
│ - Pages publik │ │ - RESTful endpoints │
│ - Admin dashboard │ │ - File upload │
│ - Server components │ │ - Swagger docs (/api/docs│
│ - Client components │ │ - Static file serving │
└────────────┬───────────┘ └────────────┬───────────────┘
│ │
│ │
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
│ (via Prisma ORM) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Seafile File Storage │
│ (Images & Documents) │
└─────────────────────────────────────────────────────────┘
```
### 3.2 Next.js App Router
- Menggunakan **App Router** (bukan Pages Router)
- Route groups `(dashboard)`, `(pages)`, `(tambahan)` untuk organisasi tanpa mempengaruhi URL
- Layout bersarang: root layout -> admin/darmasaba layout -> page layouts
- `force-dynamic` digunakan untuk menghindari error prerendering
- View Transitions API diaktifkan via `next-view-transitions`
### 3.3 Elysia.js API Server
- Terintegrasi sebagai **catch-all route** di `/api/[[...slugs]]/route.ts`
- Semua HTTP methods (GET, POST, PATCH, DELETE, PUT) di-handle oleh Elysia
- Plugin yang digunakan:
- `@elysiajs/cors` - CORS configuration
- `@elysiajs/static` - Static file serving dari `/uploads`
- `@elysiajs/swagger` - API documentation di `/api/docs`
- `@elysiajs/jwt` - JWT authentication
- `@elysiajs/cookie` - Cookie handling
- Endpoint file upload: `/api/upl-img`, `/api/upl-img-single`, `/api/upl-csv`
- Image serving: `/api/img/:name` dengan resize support
### 3.4 Rendering Strategy
- **Server Components**: Halaman publik untuk SEO optimal
- **Client Components**: Komponen interaktif (form, state, animasi)
- **Force Dynamic**: Beberapa halaman menggunakan `force-dynamic`
- **ISR**: Caching header untuk assets (1 jam cache)
---
## 4. Modul Domain
### 4.1 Profil Desa (Desa)
**Admin**: `/admin/desa/*` | **Publik**: `/darmasaba/desa/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `berita` | CRUD berita/pengumuman desa |
| `gallery` | Galeri foto dan video |
| `layanan` | Manajemen layanan desa |
| `penghargaan` | Penghargaan yang diraih |
| `pengumuman` | Pengumuman publik |
| `potensi` | Potensi desa (pertanian, pariwisata, dll) |
| `profil` | Profil desa (sejarah, visi misi, lambang, maskot, perangkat) |
### 4.2 PPID (Pejabat Pengelola Informasi dan Dokumentasi)
**Admin**: `/admin/ppid/*` | **Publik**: `/darmasaba/ppid/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `profil-ppid` | Profil pejabat PPID |
| `struktur-ppid` | Struktur organisasi PPID |
| `visi-misi-ppid` | Visi dan misi PPID |
| `daftar-informasi-publik` | Daftar informasi yang tersedia |
| `dasar-hukum` | Dasar hukum PPID |
| `permohonan-informasi-publik` | Form permohonan informasi |
| `permohonan-keberatan-informasi-publik` | Form keberatan |
| `indeks-kepuasan-masyarakat` | Survei kepuasan masyarakat (IKM) |
### 4.3 Kesehatan
**Admin**: `/admin/kesehatan/*` | **Publik**: `/darmasaba/kesehatan/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `fasilitas-kesehatan` | Data puskesmas, klinik, dokter |
| `posyandu` | Manajemen posyandu |
| `program-kesehatan` | Program kesehatan desa |
| `info-wabah-penyakit` | Informasi wabah |
| `penanganan-darurat` | Prosedur penanganan darurat |
| `kontak-darurat` | Kontak darurat kesehatan |
| `data-kesehatan-warga` | Statistik kesehatan warga |
| `artikel-kesehatan` | Artikel kesehatan |
### 4.4 Ekonomi
**Admin**: `/admin/ekonomi/*` | **Publik**: `/darmasaba/ekonomi/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `APBDes` | Anggaran Pendapatan dan Belanja Desa (hierarki items + realisasi) |
| `PADesa-pendapatan-asli-desa` | Pendapatan asli desa |
| `demografi-pekerjaan` | Demografi pekerjaan penduduk |
| `jumlah-penduduk-miskin` | Data penduduk miskin |
| `jumlah-pengangguran` | Data pengangguran |
| `lowongan-kerja-lokal` | Lowongan kerja lokal |
| `pasar-desa` | Data pasar desa |
| `program-kemiskinan` | Program penanganan kemiskinan |
| `sektor-unggulan-desa` | Sektor unggulan ekonomi |
| `Struktur-Organisasi-Dan-Sk-Pengurus-BumDes` | Struktur BUMDes |
### 4.5 Kependudukan
**Admin**: `/admin/kependudukan/*` | **Publik**: `/darmasaba/kependudukan/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `data-banjar` | Data banjar (unit wilayah tradisional Bali) |
| `distribusi-agama` | Distribusi agama penduduk |
| `distribusi-umur` | Distribusi umur penduduk |
| `migrasi-penduduk` | Data migrasi (masuk/keluar) |
### 4.6 Pendidikan
**Admin**: `/admin/pendidikan/*` | **Publik**: `/darmasaba/pendidikan/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `beasiswa-desa` | Program beasiswa |
| `bimbingan-belajar-desa` | Bimbingan belajar |
| `data-pendidikan` | Data statistik pendidikan |
| `info-sekolah` | Informasi sekolah |
| `pendidikan-non-formal` | Pendidikan non-formal |
| `perpustakaan-digital` | Perpustakaan digital |
| `program-pendidikan-anak` | Program pendidikan anak |
### 4.7 Keamanan
**Admin**: `/admin/keamanan/*` | **Publik**: `/darmasaba/keamanan/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `keamanan-lingkungan-pecalang-patwal` | Keamanan lingkungan (pecalang Bali) |
| `kontak-darurat` | Kontak darurat keamanan |
| `laporan-publik` | Laporan publik |
| `pencegahan-kriminalitas` | Pencegahan kriminalitas |
| `polsek-terdekat` | Data polsek terdekat |
| `tips-keamanan` | Tips keamanan |
### 4.8 Lingkungan
**Admin**: `/admin/lingkungan/*` | **Publik**: `/darmasaba/lingkungan/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `data-lingkungan-desa` | Data lingkungan desa |
| `edukasi-lingkungan` | Edukasi lingkungan |
| `gotong-royong` | Kegiatan gotong royong |
| `konservasi-adat-bali` | Konservasi adat Bali |
| `pengelolaan-sampah-bank-sampah` | Bank sampah |
| `program-penghijauan` | Program penghijauan |
### 4.9 Inovasi
**Admin**: `/admin/inovasi/*` | **Publik**: `/darmasaba/inovasi/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `ajukan-ide-inovatif` | Form pengajuan ide inovatif |
| `desa-digital-smart-village` | Program desa digital |
| `info-teknologi-tepat-guna` | Info teknologi tepat guna |
| `kolaborasi-inovasi` | Kolaborasi inovasi |
| `layanan-online-desa` | Layanan online desa |
| `program-kreatif-desa` | Program kreatif desa |
### 4.10 Musik Desa
**Admin**: `/admin/musik/*` | **Publik**: `/darmasaba/musik/*`
- Manajemen audio dan cover musik desa
- Fixed player bar di halaman publik
- Context provider untuk state pemutar musik
### 4.11 Landing Page
**Admin**: `/admin/landing-page/*`
| Sub-modul | Fungsi |
|-----------|--------|
| `desa-anti-korupsi` | Konten anti-korupsi |
| `prestasi-desa` | Prestasi yang diraih |
| `sdgs-desa` | SDGs (Sustainable Development Goals) |
| `profil-landing-page` | Profil dan media sosial |
### 4.12 User & Role
**Admin**: `/admin/user&role/*`
- Manajemen pengguna admin
- Manajemen role dan permission
- Manajemen menu akses
---
## 5. Database Schema
### 5.1 Overview
Database menggunakan **PostgreSQL** dengan **Prisma ORM** (versi 6.3.1).
Schema terdiri dari **2413 baris** dengan **100+ model**.
### 5.2 Model Utama
#### FileStorage
Model sentral untuk semua file (gambar, dokumen, audio):
```prisma
model FileStorage {
id String @id @default(cuid())
name String @unique
realName String
path String
mimeType String
category String // "image" / "document" / "audio" / "other"
link String
isActive Boolean @default(true)
// Relasi ke 50+ model lain (Berita, PotensiDesa, GalleryFoto, dll)
}
```
#### AppMenu & AppMenuChild
Menu navigasi aplikasi:
```prisma
model AppMenu {
id String @id @default(cuid())
name String @unique
link String
isActive Boolean @default(true)
AppMenuChild AppMenuChild[]
}
```
#### User & Role (Autentikasi Admin)
- `User` - Data pengguna admin
- `Role` - Role/peran pengguna
- `Menu` - Menu akses per role
#### Modul Desa
- `Berita` - Berita desa (dengan featured image & gallery)
- `GalleryFoto` / `GalleryVideo` - Galeri media
- `Layanan` - Layanan desa
- `Pengumuman` - Pengumuman
- `PotensiDesa` - Potensi desa
- `ProfileDesaImage` - Gambar profil desa
- `ProfilPerbekel` - Profil perbekel (kepala desa)
- `PejabatDesa` - Pejabat desa
- `Penghargaan` - Penghargaan
- `PrestasiDesa` - Prestasi
- `MediaSosial` - Media sosial desa
#### Modul PPID
- `StrukturPPID` - Struktur organisasi
- `PosisiOrganisasiPPID` - Posisi dengan hierarki
- `PegawaiPPID` - Data pegawai
- `ProfilePPID` - Profil PPID
- `VisiMisiPPID` - Visi misi
- `DasarHukumPPID` - Dasar hukum
- `DaftarInformasiPublik` - Daftar informasi
- `PermohonanInformasiPublik` - Permohonan informasi
- `FormulirPermohonanKeberatan` - Formulir keberatan
- `IndeksKepuasanMasyarakat` - IKM
- `Responden` + lookup tables - Data responden IKM
#### Modul Kesehatan
- `Puskesmas` - Data puskesmas
- `Posyandu` - Data posyandu
- `ProgramKesehatan` - Program kesehatan
- `FasilitasKesehatan` - Fasilitas
- `InfoWabahPenyakit` - Info wabah
- `PenangananDarurat` - Penanganan darurat
- `KontakDarurat` - Kontak darurat
- `ArtikelKesehatan` - Artikel
#### Modul Ekonomi
- `APBDes` & `APBDesItem` - Anggaran desa (hierarki tree structure)
- `RealisasiItem` - Realisasi anggaran (multiple per item)
- `PasarDesa` - Pasar desa
- `PegawaiBumDes` - Pegawai BUMDes
- `StrukturBumDes` - Struktur BUMDes
- `DemografiPekerjaan` - Demografi pekerjaan
- `JumlahPendudukMiskin` - Data kemiskinan
- `JumlahPengangguran` - Data pengangguran
- `LowonganKerjaLokal` - Lowongan kerja
- `ProgramKemiskinan` - Program kemiskinan
- `SektorUnggulanDesa` - Sektor unggulan
- `PendapatanAsli` - Pendapatan asli desa
#### Modul Kependudukan
- `DataBanjar` - Data banjar
- `DistribusiAgama` - Distribusi agama
- `DistribusiUmur` - Distribusi umur
- `MigrasiPenduduk` - Migrasi
#### Modul Pendidikan
- `InfoSekolah` - Data sekolah
- `BeasiswaDesa` - Beasiswa
- `BimbinganBelajar` - Bimbingan belajar
- `PendidikanNonFormal` - Pendidikan non-formal
- `DataPerpustakaan` - Perpustakaan
#### Modul Keamanan
- `KeamananLingkungan` - Keamanan lingkungan
- `MenuTipsKeamanan` - Tips keamanan
- `PencegahanKriminalitas` - Pencegahan kriminalitas
- `PolsekTerdekat` - Polsek terdekat
- `LaporanPublik` - Laporan publik
#### Modul Lingkungan
- `DataLingkunganDesa` - Data lingkungan
- `KonservasiAdatBali` - Konservasi adat
- `BankSampah` - Bank sampah
- `ProgramPenghijauan` - Penghijauan
- `GotongRoyong` - Gotong royong
- `EdukasiLingkungan` - Edukasi
#### Modul Inovasi
- `ProgramInovasi` - Program inovasi
- `DesaDigital` - Desa digital
- `InfoTekno` - Info teknologi
- `KolaborasiInovasi` + `MitraKolaborasi` - Kolaborasi
- `LayananOnlineDesa` - Layanan online
- `ProgramKreatifDesa` - Program kreatif
- `Ajukan` - Pengajuan ide
#### Modul Musik
- `MusikDesa` - Musik desa
- `audioFile` -> FileStorage
- `coverImage` -> FileStorage
#### Landing Page
- `DesaAntiKorupsi` + `KategoriDesaAntiKorupsi`
- `SdgsDesa` - SDGs
- `PrestasiDesa` + `KategoriPrestasiDesa`
- `MediaSosial`
- `LandingPage_Layanan`
#### APBDes (Struktur Hierarki)
```prisma
model APBDesItem {
kode String // "4", "4.1", "4.1.2"
uraian String // Nama item
anggaran Float // Anggaran dalam Rupiah
tipe String? // "pendapatan" | "belanja" | "pembiayaan"
level Int // 1, 2, 3
parentId String? // Self-referencing untuk tree
children APBDesItem[]
totalRealisasi Float @default(0) // Auto-calculated
selisih Float @default(0) // totalRealisasi - anggaran
persentase Float @default(0) // (totalRealisasi / anggaran) * 100
realisasiItems RealisasiItem[]
}
```
### 5.3 Pola Umum Model
Hampir semua model mengikuti pola:
```prisma
model Contoh {
id String @id @default(cuid())
// ... fields
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? @default(now()) // Soft delete
isActive Boolean @default(true) // Soft delete flag
}
```
---
## 6. API Routes
### 6.1 Struktur API
Semua API routes ditangani oleh **Elysia.js** di `/src/app/api/[[...slugs]]/route.ts`
### 6.2 API Groups
| Prefix | Modul | Contoh Endpoints |
|--------|-------|------------------|
| `/api/layanan` | Layanan | `GET /api/layanan` |
| `/api/potensi` | Potensi | `GET /api/potensi` |
| `/api/desa/*` | Desa | CRUD berita, gallery, profil, dll |
| `/api/ppid/*` | PPID | CRUD struktur, profil, permohonan |
| `/api/kesehatan/*` | Kesehatan | CRUD puskesmas, posyandu, dll |
| `/api/ekonomi/*` | Ekonomi | CRUD APBDes, BUMDes, demografi |
| `/api/kependudukan/*` | Kependudukan | CRUD banjar, demografi |
| `/api/pendidikan/*` | Pendidikan | CRUD sekolah, beasiswa |
| `/api/keamanan/*` | Keamanan | CRUD keamanan, kontak darurat |
| `/api/lingkungan/*` | Lingkungan | CRUD data lingkungan |
| `/api/inovasi/*` | Inovasi | CRUD program inovasi |
| `/api/landing-page/*` | Landing Page | CRUD konten landing page |
| `/api/user/*` | User | CRUD user admin |
| `/api/user/role/*` | Role | CRUD role & permission |
| `/api/search` | Search | Pencarian global |
| `/api/file-storage/*` | File Storage | CRUD file storage |
| `/api/img/:name` | Image | GET gambar dengan resize |
| `/api/upl-img` | Upload | Upload multiple images |
| `/api/upl-img-single` | Upload | Upload single image |
| `/api/upl-csv` | Upload | Upload CSV files |
| `/api/utils/version` | Utils | GET versi aplikasi |
### 6.3 API Documentation
Swagger UI tersedia di: **`/api/docs`**
### 6.4 API Route Lainnya
| Route | Fungsi |
|-------|--------|
| `/api/health` | Health check endpoint |
| `/api/news` | API berita (standalone) |
| `/api/subscribe` | Subscription email |
| `/api/tts` | Text-to-Speech (ElevenLabs) |
| `/api/admin/*` | API khusus admin |
| `/api/auth/*` | API autentikasi |
---
## 7. Halaman Admin
### 7.1 Struktur
Admin dashboard berada di `/admin` dengan route group `(dashboard)`.
| Section | Path | Fungsi |
|---------|------|--------|
| **Dashboard** | `/admin` | Dashboard utama |
| **Autentikasi** | `/admin/auth` | Login admin |
| **Desa** | `/admin/desa/*` | Berita, gallery, profil, layanan, penghargaan, pengumuman, potensi |
| **PPID** | `/admin/ppid/*` | Profil, struktur, visi-misi, daftar informasi, dasar hukum, permohonan, IKM |
| **Kesehatan** | `/admin/kesehatan/*` | Puskesmas, posyandu, program kesehatan, wabah, kontak darurat |
| **Ekonomi** | `/admin/ekonomi/*` | APBDes, PAD, demografi, pengangguran, kemiskinan, BUMDes, pasar desa |
| **Kependudukan** | `/admin/kependudukan/*` | Banjar, distribusi agama, distribusi umur, migrasi |
| **Pendidikan** | `/admin/pendidikan/*` | Sekolah, beasiswa, bimbingan belajar, perpustakaan digital |
| **Keamanan** | `/admin/keamanan/*` | Keamanan lingkungan, kontak darurat, pencegahan kriminalitas, polsek |
| **Lingkungan** | `/admin/lingkungan/*` | Data lingkungan, konservasi, bank sampah, penghijauan, gotong royong |
| **Inovasi** | `/admin/inovasi/*` | Ide inovatif, desa digital, teknologi tepat guna, kolaborasi |
| **Musik** | `/admin/musik/*` | Manajemen musik desa |
| **Landing Page** | `/admin/landing-page/*` | Anti-korupsi, prestasi, SDGs, media sosial |
| **User & Role** | `/admin/user&role/*` | Manajemen user dan role |
| **Images** | `/admin/images/*` | Manajemen gambar |
| **CSV** | `/admin/csv/*` | Upload/import CSV |
### 7.2 Komponen Admin Shared
- `AdminThemeProvider.tsx` - Theme provider untuk dark/light mode
- `DarkModeToggle.tsx` - Toggle dark mode
- `UnifiedSurface.tsx` - Komponen surface/card unified
- `UnifiedTypography.tsx` - Tipografi unified
---
## 8. Halaman Publik
### 8.1 Struktur
Halaman publik berada di `/darmasaba` dengan layout yang mencakup Navbar, Footer, dan Fixed Music Player.
| Halaman | Path | Konten |
|---------|------|--------|
| **Landing Page

842
STRUKTUR.md Normal file
View File

@@ -0,0 +1,842 @@
# Dokumentasi Struktur Proyek Desa Darmasaba
## 1. Ringkasan Proyek
**Desa Darmasaba** adalah aplikasi web manajemen desa digital untuk Desa Darmasaba, Kabupaten Badung, Bali. Aplikasi ini berfungsi sebagai platform layanan publik digital yang mencakup informasi pemerintahan, layanan kesehatan, keamanan, pendidikan, ekonomi, lingkungan, dan inovasi desa.
### Tech Stack
| Kategori | Teknologi |
|----------|-----------|
| **Framework** | Next.js 15 (App Router) |
| **Language** | TypeScript (strict mode) |
| **Runtime** | Bun |
| **Backend API** | Elysia.js (high-performance HTTP server) |
| **Database** | PostgreSQL |
| **ORM** | Prisma 6.3.1 |
| **UI Framework** | Mantine UI v7-v8 |
| **State Management** | Jotai + Valtio + SWR |
| **Authentication** | iron-session + JWT (@elysiajs/jwt) |
| **File Storage** | Seafile (self-hosted) |
| **Text Editor** | Tiptap (Rich text editor) |
| **Charts** | Recharts + Chart.js |
| **Maps** | Leaflet + react-leaflet |
| **Testing** | Vitest (unit) + Playwright (E2E) |
| **Styling** | Mantine + PostCSS + Framer Motion |
| **Deployment** | Docker + GHCR + Portainer + GitHub Actions |
| **Version** | 0.1.11 |
---
## 2. Struktur Direktori
```
desa-darmasaba/
├── .github/workflows/ # GitHub Actions CI/CD
│ ├── docker-publish.yml # Auto build & push saat tag v*
│ ├── publish.yml # Manual build & push ke GHCR
│ ├── re-pull.yml # Manual re-pull di Portainer
│ └── script/ # Shell scripts untuk deploy
├── prisma/
│ ├── schema.prisma # Database schema (2413 baris, 100+ model)
│ └── seed.ts # Database seeder (400+ baris)
│ └── _seeder_list/ # Seed data per modul
├── public/ # Static assets
│ └── assets/
│ └── images/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── _com/ # Global components (SplashScreen, WebVitals)
│ │ ├── admin/ # ADMIN DASHBOARD
│ │ │ ├── (dashboard)/ # Route group dashboard
│ │ │ │ ├── desa/ # - Berita, Gallery, Layanan, dll
│ │ │ │ ├── ppid/ # - Informasi publik, struktur, dasar hukum
│ │ │ │ ├── kesehatan/ # - Fasilitas, posyandu, puskesmas, wabah
│ │ │ │ ├── ekonomi/ # - APBDes, pasar desa, BUMDes, dll
│ │ │ │ ├── kependudukan/ # - Banjar, agama, umur, migrasi
│ │ │ │ ├── pendidikan/ # - Sekolah, beasiswa, perpustakaan
│ │ │ │ ├── keamanan/ # - Keamanan lingkungan, polsek, dll
│ │ │ │ ├── lingkungan/ # - Sampah, penghijauan, gotong royong
│ │ │ │ ├── inovasi/ # - Desa digital, kolaborasi, dll
│ │ │ │ ├── landing-page/ # - Profil, prestasi, anti-korupsi
│ │ │ │ ├── musik/ # - Musik desa
│ │ │ │ ├── user&role/ # - Manajemen user & role
│ │ │ │ └── _com/ # - Shared admin components
│ │ │ ├── auth/ # Login OTP untuk admin
│ │ │ ├── csv/ # Demo CSV upload
│ │ │ └── layout.tsx # Admin shell (AppShell Mantine)
│ │ ├── api/ # ELYSIA.JS API SERVER
│ │ │ ├── [[...slugs]]/ # Catch-all route -> Elysia handler
│ │ │ │ ├── route.ts # - Main Elysia server export
│ │ │ │ └── _lib/ # - Domain route modules
│ │ │ │ ├── desa.ts
│ │ │ │ ├── ppid.ts
│ │ │ │ ├── kesehatan.ts
│ │ │ │ ├── ekonomi.ts
│ │ │ │ ├── keamanan.ts
│ │ │ │ ├── inovasi.ts
│ │ │ │ ├── lingkungan.ts
│ │ │ │ ├── pendidikan.ts
│ │ │ │ ├── kependudukan.ts
│ │ │ │ ├── landing_page.ts
│ │ │ │ ├── user/ # - User & Role management
│ │ │ │ ├── fileStorage/
│ │ │ │ ├── search/
│ │ │ │ ├── auth/
│ │ │ │ ├── upl-img.ts, upl-img-single.ts
│ │ │ │ ├── upl-csv.ts, upl-csv-single.ts
│ │ │ │ └── img.ts, img-del.ts, imgs.ts
│ │ │ ├── auth/ # Auth endpoints (login, logout, me)
│ │ │ └── ... # Other API routes
│ │ ├── darmasaba/ # PUBLIC-FACING WEBSITE
│ │ │ ├── _com/ # Shared components (Navbar, Footer, etc)
│ │ │ ├── (pages)/ # Public pages route group
│ │ │ │ ├── desa/ # - Profil, berita, gallery, layanan
│ │ │ │ ├── ppid/ # - PPID public pages
│ │ │ │ ├── kesehatan/ # - Health info pages
│ │ │ │ ├── ekonomi/ # - Economy pages
│ │ │ │ ├── kependudukan/
│ │ │ │ ├── pendidikan/
│ │ │ │ ├── keamanan/
│ │ │ │ ├── lingkungan/
│ │ │ │ ├── inovasi/
│ │ │ │ ├── musik/
│ │ │ │ └── module/ # - External module links
│ │ │ └── (tambahan)/ # Additional pages
│ │ ├── login/ # Login page
│ │ ├── registrasi/ # Registration page
│ │ ├── waiting-room/ # Waiting room (inactive users)
│ │ ├── terms-of-service/
│ │ ├── layout.tsx # Root layout (MantineProvider, ViewTransitions)
│ │ └── page.tsx # Homepage redirect
│ ├── components/
│ │ └── admin/ # Admin shared components
│ │ ├── AdminThemeProvider.tsx
│ │ ├── DarkModeToggle.tsx
│ │ ├── UnifiedSurface.tsx
│ │ └── UnifiedTypography.tsx
│ ├── con/ # Constants & configuration
│ │ ├── colors.ts # Color palette definitions
│ │ ├── images.ts
│ │ ├── navbar-list-menu.ts
│ │ ├── router.ts # Route mapping
│ │ └── sosmed.ts
│ ├── context/ # React contexts
│ │ └── MusicContext.tsx # Music player context
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utility libraries
│ │ ├── router/
│ │ ├── api-auth.ts # API authentication helpers
│ │ ├── api-fetch.ts # API fetch wrapper
│ │ ├── EnvStringParse.ts
│ │ ├── prisma.ts # Prisma client singleton
│ │ ├── seafile-auth-service.ts
│ │ └── session.ts # iron-session helper
│ ├── state/ # Global state (Jotai/Valtio)
│ │ ├── darkModeStore.ts
│ │ ├── state-layanan.ts
│ │ ├── state-list-image.ts
│ │ └── state-nav.ts
│ ├── store/ # Additional stores
│ │ └── authStore.ts # Auth state (Jotai)
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Utility functions
│ └── themeTokens.ts # Dark/light theme tokens
├── uploads/ # Local upload directory (images/files)
├── Dockerfile # Multi-stage Docker build (Bun)
├── docker-entrypoint.sh # Entry script (migrate + start)
├── next.config.ts # Next.js configuration
├── package.json # Dependencies & scripts
├── tsconfig.json # TypeScript configuration
├── biome.json # Biome linter config
├── eslint.config.mjs # ESLint config
├── NOTE.md # Deployment notes
├── QWEN.md # Project memory & workflow
└── AGENTS.md # Agent coding guidelines
```
---
## 3. Arsitektur
### Pola Arsitektur: Full-Stack Monolith dengan App Router
```
Browser
|
+-- Next.js 15 (App Router) -- Server Components + Client Components
|
+-- /darmasaba/* -> Public pages (SSR/CSR)
+-- /admin/* -> Admin dashboard (protected)
+-- /api/* -> Elysia.js API server
|
+-- Elysia Server (src/app/api/[[...slugs]]/route.ts)
|
+-- CORS enabled
+-- Swagger docs di /api/docs
+-- Static file serving (/api/uploads)
+-- Domain modules: Desa, PPID, Kesehatan, Ekonomi, dll
+-- Image upload handlers
|
+-- Prisma ORM --> PostgreSQL
+-- Seafile API --> File Storage
```
### Key Architectural Decisions:
1. **Next.js 15 App Router**: Menggunakan React Server Components sebagai default, dengan `"use client"` untuk interaktivitas
2. **Elysia.js di dalam API Routes**: Catch-all route `[[...slugs]]` meneruskan semua request ke Elysia handler
3. **Route Groups**: `(dashboard)` dan `(pages)` untuk organisasi tanpa mempengaruhi URL path
4. **Multi-tenant Ready**: Role-based access control dengan dynamic navbar berdasarkan roleId
5. **File Uploads**: Local uploads + Seafile integration untuk distributed storage
---
## 4. Modul Domain
### A. PPID (Pejabat Pengelola Informasi dan Dokumentasi)
**Lokasi**: `src/app/admin/(dashboard)/ppid/` dan `src/app/darmasaba/(pages)/ppid/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Profil PPID | Profil pejabat pengelola informasi |
| Struktur PPID | Struktur organisasi PPID dengan hierarki |
| Visi & Misi PPID | Visi dan misi PPID desa |
| Daftar Informasi Publik | Katalog informasi publik yang tersedia |
| Dasar Hukum | Regulasi dan dasar hukum PPID |
| Permohonan Informasi Publik | Form permohonan informasi (NIK, kontak, jenis) |
| Permohonan Keberatan | Formulir keberatan informasi |
| Indeks Kepuasan Masyarakat | Survey kepuasan dengan grafik demografis |
### B. Desa (Landing Page & Umum)
**Lokasi**: `src/app/admin/(dashboard)/desa/` dan `src/app/darmasaba/(pages)/desa/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Profil Desa | Sejarah, visi-misi, lambang, maskot |
| Profil Perbekel | Biodata, pengalaman, program unggulan perbekel |
| Perbekel dari Masa ke Masa | Historis perbekel per periode |
| Berita | Artikel berita dengan kategori & multi-image |
| Gallery | Foto dan video galeri |
| Pengumuman | Pengumuman desa dengan kategori |
| Potensi Desa | Potensi desa dengan kategori |
| Layanan Desa | Surat keterangan, ajukan permohonan |
| Penghargaan | Prestasi dan penghargaan desa |
| Desa Anti Korupsi | Transparansi anti-korupsi |
| SDGs Desa | Sustainable Development Goals desa |
| APBDes | Anggaran desa dengan hierarki item & realisasi |
| Prestasi Desa | Katalog prestasi |
### C. Kesehatan
**Lokasi**: `src/app/admin/(dashboard)/kesehatan/` dan `src/app/darmasaba/(pages)/kesehatan/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Fasilitas Kesehatan | Info rumah sakit/klinik (jam, dokter, tarif) |
| Puskesmas | Data puskesmas dengan jam operasional & kontak |
| Posyandu | Jadwal dan informasi posyandu |
| Program Kesehatan | Program-program kesehatan desa |
| Penanganan Darurat | Prosedur penanganan darurat |
| Kontak Darurat | Kontak emergency dengan WhatsApp |
| Info Wabah Penyakit | Informasi wabah penyakit |
| Artikel Kesehatan | Artikel kesehatan lengkap |
| Data Kesehatan Warga | Statistik kesehatan warga |
| Kelahiran & Kematian | Data vital statistik |
| Grafik Kepuasan | Grafik kepuasan layanan kesehatan |
### D. Ekonomi
**Lokasi**: `src/app/admin/(dashboard)/ekonomi/` dan `src/app/darmasaba/(pages)/ekonomi/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Pasar Desa | Katalog pasar desa dengan produk & rating |
| Struktur BUMDes | Organisasi BUMDes dengan pengurus |
| APBDes (PADesa) | Pendapatan Asli Desa |
| Program Kemiskinan | Program dan statistik kemiskinan |
| Sektor Unggulan | Sektor ekonomi unggulan desa |
| Lowongan Kerja Lokal | Info lowongan pekerjaan |
| Demografi Pekerjaan | Distribusi pekerjaan penduduk |
| Jumlah Pengangguran | Statistik pengangguran |
| Penduduk Usia Kerja Menganggur | Analisis pengangguran by usia & pendidikan |
| Jumlah Penduduk Miskin | Tren kemiskinan tahunan |
### E. Kependudukan
**Lokasi**: `src/app/admin/(dashboard)/kependudukan/` dan `src/app/darmasaba/(pages)/kependudukan/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Data Banjar | Data penduduk per banjar |
| Distribusi Agama | Statistik agama penduduk |
| Distribusi Umur | Piramida umur penduduk |
| Migrasi Penduduk | Data migrasi masuk/keluar |
| Dinamika Penduduk | Kelahiran, kematian, migrasi per tahun |
### F. Pendidikan
**Lokasi**: `src/app/admin/(dashboard)/pendidikan/` dan `src/app/darmasaba/(pages)/pendidikan/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Info Sekolah & PAUD | Data sekolah per jenjang (TK, SD, SMP, SMA) |
| Beasiswa Desa | Program beasiswa & pendaftar |
| Program Pendidikan Anak | Program pendidikan anak |
| Bimbingan Belajar | Informasi bimbingan belajar |
| Pendidikan Non Formal | Tempat & program non-formal |
| Perpustakaan Digital | Katalog buku & peminjaman |
| Data Pendidikan | Statistik pendidikan |
### G. Keamanan
**Lokasi**: `src/app/admin/(dashboard)/keamanan/` dan `src/app/darmasaba/(pages)/keamanan/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Keamanan Lingkungan (Pecalang/Patwal) | Sistem keamanan tradisional Bali |
| Polsek Terdekat | Data polsek dengan layanan & map |
| Kontak Darurat | Kontak darurat keamanan |
| Pencegahan Kriminalitas | Info pencegahan kriminal |
| Laporan Publik | Laporan masyarakat dengan tracking status |
| Tips Keamanan | Tips dan panduan keamanan |
### H. Lingkungan
**Lokasi**: `src/app/admin/(dashboard)/lingkungan/` dan `src/app/darmasaba/(pages)/lingkungan/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Pengelolaan Sampah | Bank sampah & pengelolaan |
| Program Penghijauan | Program penghijauan desa |
| Data Lingkungan | Data lingkungan desa |
| Gotong Royong | Kegiatan gotong royong |
| Edukasi Lingkungan | Edukasi lingkungan hidup |
| Konservasi Adat Bali | Tri Hita Karana & konservasi adat |
### I. Inovasi
**Lokasi**: `src/app/admin/(dashboard)/inovasi/` dan `src/app/darmasaba/(pages)/inovasi/`
| Sub-modul | Deskripsi |
|-----------|-----------|
| Desa Digital (Smart Village) | Transformasi digital desa |
| Program Kreatif Desa | Program kreatif & inovatif |
| Kolaborasi Inovasi | Kolaborasi dengan mitra |
| Info Teknologi Tepat Guna | Info teknologi untuk desa |
| Ajukan Ide Inovatif | Form pengajuan ide dari warga |
| Layanan Online Desa | Layanan administrasi online |
### J. Musik Desa
**Lokasi**: `src/app/admin/(dashboard)/musik/` dan `src/app/darmasaba/(pages)/musik/`
Model `MusikDesa` dengan audio file, cover image, genre, dan durasi. Dilengkapi dengan `FixedPlayerBar` di layout publik.
### K. User & Role (Admin)
**Lokasi**: `src/app/admin/(dashboard)/user&role/`
- **Role-based Access Control**: Role dengan permission JSON
- **User Session Management**: Multiple sessions per user dengan JWT
- **OTP Authentication**: Login dengan nomor telepon + OTP
- **Menu Access Control**: Dynamic navbar berdasarkan menu akses user
---
## 5. Database Schema (Prisma)
Schema terdiri dari **2413 baris** dengan **100+ model** dan **berbagai enum**. Berikut model-model utama:
### Core Models
| Model | Keterangan |
|-------|-----------|
| `FileStorage` | Central file storage untuk semua uploaded files |
| `AppMenu` / `AppMenuChild` | Menu navigasi aplikasi |
| `User` / `Role` / `UserSession` / `UserMenuAccess` | Sistem autentikasi & otorisasi |
| `KodeOtp` | OTP codes untuk login |
### Landing Page & Desa
| Model | Keterangan |
|-------|-----------|
| `PejabatDesa` | Pejabat desa dengan foto |
| `ProfilPerbekel` | Profil perbekel (biodata, pengalaman, program) |
| `PerbekelDariMasaKeMasa` | Historis perbekel |
| `Berita` / `KategoriBerita` | Berita desa |
| `PotensiDesa` / `KategoriPotensi` | Potensi desa |
| `Pengumuman` / `CategoryPengumuman` | Pengumuman |
| `GalleryFoto` / `GalleryVideo` | Gallery media |
| `Penghargaan` | Penghargaan desa |
| `APBDes` / `APBDesItem` / `RealisasiItem` | Anggaran dengan realisasi |
| `DesaAntiKorupsi` / `KategoriDesaAntiKorupsi` | Transparansi |
| `SdgsDesa` | SDGs desa |
| `PrestasiDesa` / `KategoriPrestasiDesa` | Prestasi |
| `MusikDesa` | Musik desa |
### PPID
| Model | Keterangan |
|-------|-----------|
| `StrukturPPID` / `PosisiOrganisasiPPID` / `PegawaiPPID` | Struktur organisasi |
| `VisiMisiPPID` | Visi misi |
| `ProfilePPID` | Profil pejabat |
| `DasarHukumPPID` | Regulasi |
| `DaftarInformasiPublik` | Katalog informasi |
| `PermohonanInformasiPublik` | Permohonan + lookup tables |
| `FormulirPermohonanKeberatan` | Keberatan |
| `IndeksKepuasanMasyarakat` + grafik breakdown | Survey kepuasan |
### Kesehatan
| Model | Keterangan |
|-------|-----------|
| `FasilitasKesehatan` | Fasilitas lengkap (dokter, tarif, prosedur) |
| `Puskesmas` / `JamOperasional` / `KontakPuskesmas` | Puskesmas |
| `Posyandu` | Pos pelayanan terpadu |
| `ProgramKesehatan` | Program kesehatan |
| `ArtikelKesehatan` | Artikel lengkap (gejala, pencegahan, P3K, dll) |
| `PenangananDarurat` / `KontakDarurat` | Darurat |
| `InfoWabahPenyakit` | Wabah |
| `DataKematian_Kelahiran` / `Kelahiran` / `Kematian` | Vital statistik |
| `GrafikKepuasan` | Kepuasan |
### Ekonomi
| Model | Keterangan |
|-------|-----------|
| `PasarDesa` / `KategoriProduk` / `KategoriToPasar` | Pasar desa |
| `StrukturBumDes` / `PosisiOrganisasiBumDes` / `PegawaiBumDes` | BUMDes |
| `ProgramKemiskinan` / `StatistikKemiskinan` | Kemiskinan |
| `SektorUnggulanDesa` | Sektor unggulan |
| `LowonganPekerjaan` | Lowongan |
| `DataDemografiPekerjaan` | Demografi pekerjaan |
| `DetailDataPengangguran` | Pengangguran |
| `GrafikJumlahPendudukMiskin` | Tren kemiskinan |
### Kependudukan
| Model | Keterangan |
|-------|-----------|
| `DataBanjar` | Data per banjar |
| `DistribusiAgama` | Distribusi agama |
| `DistribusiUmur` | Distribusi umur |
| `MigrasiPenduduk` | Migrasi (MASUK/KELUAR) |
| `DinamikaPenduduk` | Dinamika tahunan |
### Pendidikan
| Model | Keterangan |
|-------|-----------|
| `JenjangPendidikan` / `Lembaga` / `Siswa` / `Pengajar` | Data sekolah |
| `BeasiswaPendaftar` | Beasiswa (dengan enum lengkap) |
| `DataPerpustakaan` / `KategoriBuku` / `PeminjamanBuku` | Perpustakaan |
| `DataPendidikan` | Statistik |
### Keamanan
| Model | Keterangan |
|-------|-----------|
| `KeamananLingkungan` | Keamanan lingkungan |
| `PolsekTerdekat` / `LayananPolsek` / `LayananToPolsek` | Polsek |
| `KontakDaruratKeamanan` / `KontakItem` | Kontak darurat |
| `PencegahanKriminalitas` | Pencegahan |
| `LaporanPublik` / `PenangananLaporanPublik` (enum StatusLaporan) | Laporan |
| `Pelapor` | Pelapor |
| `MenuTipsKeamanan` | Tips |
### Lingkungan
| Model | Keterangan |
|-------|-----------|
| `PengelolaanSampah` | Pengelolaan sampah |
| `KeteranganBankSampahTerdekat` | Bank sampah |
| `ProgramPenghijauan` | Penghijauan |
| `DataLingkunganDesa` | Data lingkungan |
| `KegiatanDesa` / `KategoriKegiatan` | Gotong royong |
| `FilosofiTriHita` / `BentukKonservasiBerdasarkanAdat` | Konservasi Bali |
### Inovasi
| Model | Keterangan |
|-------|-----------|
| `DesaDigital` | Smart village |
| `ProgramKreatif` | Program kreatif |
| `KolaborasiInovasi` / `MitraKolaborasi` | Kolaborasi |
| `InfoTekno` | Teknologi tepat guna |
| `AjukanIdeInovatif` | Ide dari warga |
| `AdministrasiOnline` / `JenisLayanan` | Layanan online |
| `PengaduanMasyarakat` / `JenisPengaduan` | Pengaduan |
---
## 6. API Routes
Semua API ditangani oleh **Elysia.js** di `src/app/api/[[...slugs]]/route.ts`:
| Endpoint Group | Prefix | Deskripsi |
|---------------|--------|-----------|
| **File Storage** | `/api/file-storage` | CRUD file storage |
| **Landing Page** | `/api/landing-page` | Profil, prestasi, anti-korupsi, SDGs, APBDes |
| **Desa** | `/api/desa` | Berita, gallery, potensi, pengumuman, layanan |
| **PPID** | `/api/ppid` | Semua endpoint PPID |
| **Kesehatan** | `/api/kesehatan` | Fasilitas, puskesmas, posyandu, artikel, wabah |
| **Ekonomi** | `/api/ekonomi` | Pasar desa, BUMDes, APBDes, pengangguran |
| **Keamanan** | `/api/keamanan` | Keamanan, polsek, laporan, kriminalitas |
| **Lingkungan** | `/api/lingkungan` | Sampah, penghijauan, gotong royong |
| **Pendidikan** | `/api/pendidikan` | Sekolah, beasiswa, perpustakaan |
| **Kependudukan** | `/api/kependudukan` | Banjar, agama, umur, migrasi |
| **Inovasi** | `/api/inovasi` | Desa digital, kolaborasi, pengaduan |
| **User** | `/api/admin/user` | CRUD user |
| **Role** | `/api/admin/role` | CRUD role |
| **Search** | `/api/search` | Global search |
| **Utils** | `/api/utils/version` | Version info |
### Utility Endpoints
| Endpoint | Method | Deskripsi |
|----------|--------|-----------|
| `/api/img/:name` | GET | Serve image dengan resize |
| `/api/img/:name` | DELETE | Delete image |
| `/api/imgs` | GET | List images dengan pagination |
| `/api/upl-img` | POST | Upload multiple images |
| `/api/upl-img-single` | POST | Upload single image |
| `/api/upl-csv` | POST | Upload CSV multiple |
| `/api/upl-csv-single` | POST | Upload single CSV |
### Auth Endpoints
| Endpoint | Method | Deskripsi |
|----------|--------|-----------|
| `/api/auth/login` | POST | Login dengan OTP |
| `/api/auth/logout` | POST | Logout |
| `/api/auth/me` | GET | Get current user |
**Swagger Documentation**: Tersedia di `/api/docs`
---
## 7. Halaman Admin
Admin dashboard menggunakan **Mantine AppShell** dengan sidebar navigasi dinamis berbasis role.
### Route Group: `/admin`
| Section | Path | Deskripsi |
|---------|------|-----------|
| **Landing Page** | `/admin/landing-page/` | Profil desa, prestasi, anti-korupsi, SDGs, media sosial |
| **Desa** | `/admin/desa/` | Berita, gallery, layanan, penghargaan, pengumuman, potensi, profil |
| **PPID** | `/admin/ppid/` | 8 sub-modul PPID lengkap |
| **Kesehatan** | `/admin/kesehatan/` | 8 sub-modul kesehatan |
| **Ekonomi** | `/admin/ekonomi/` | 10 sub-modul ekonomi |
| **Kependudukan** | `/admin/kependudukan/` | 4 sub-modul kependudukan |
| **Pendidikan** | `/admin/pendidikan/` | 7 sub-modul pendidikan |
| **Keamanan** | `/admin/keamanan/` | 6 sub-modul keamanan |
| **Lingkungan** | `/admin/lingkungan/` | 6 sub-modul lingkungan |
| **Inovasi** | `/admin/inovasi/` | 6 sub-modul inovasi |
| **Musik** | `/admin/musik/` | Manajemen musik desa |
| **User & Role** | `/admin/user&role/` | Manajemen user, role, menu access |
### Fitur Admin:
- **Role-based Dynamic Navbar**: Navbar berubah berdasarkan roleId user
- **Dark Mode Toggle**: Tema gelap/terang
- **OTP Login**: Login dengan nomor telepon + kode OTP
- **Session Management**: Multiple sessions per user dengan JWT tokens
- **CSV Upload**: Import data via CSV
- **Image Upload**: Upload dengan preview dan management
- **Rich Text Editor**: Tiptap untuk konten HTML
### Role-Based Redirect:
| roleId | Role | Default Redirect |
|--------|------|-----------------|
| 0, 1, 2 | Super Admin / Admin Desa | `/admin/landing-page/profil/program-inovasi` |
| 3 | Admin Kesehatan | `/admin/kesehatan/posyandu` |
| 4 | Admin Pendidikan | `/admin/pendidikan/info-sekolah/jenjang-pendidikan` |
---
## 8. Halaman Publik
Public website di `/darmasaba/` dengan layout yang mencakup **Navbar**, **Footer**, dan **Fixed Music Player Bar**.
### Route Group: `/darmasaba`
| Section | Path | Deskripsi |
|---------|------|-----------|
| **Home** | `/darmasaba` | Landing page utama |
| **Desa** | `/darmasaba/desa` | Profil, berita, gallery, layanan, pengumuman, potensi |
| **PPID** | `/darmasaba/ppid` | 7 sub-halaman PPID publik |
| **Kesehatan** | `/darmasaba/kesehatan` | Info kesehatan publik |
| **Ekonomi** | `/darmasaba/ekonomi` | Info ekonomi desa |
| **Kependudukan** | `/darmasaba/kependudukan` | Data kependudukan |
| **Pendidikan** | `/darmasaba/pendidikan` | Info pendidikan |
| **Keamanan** | `/darmasaba/keamanan` | Info keamanan |
| **Lingkungan** | `/darmasaba/lingkungan` | Info lingkungan |
| **Inovasi** | `/darmasaba/inovasi` | Info inovasi |
| **Musik** | `/darmasaba/musik` | Musik desa |
| **Module** | `/darmasaba/module/*` | Link ke modul eksternal (DAVES, MANGAN, Bicara-Darma, BARES, dll) |
### Fitur Publik:
- **Fixed Music Player Bar**: Player musik yang selalu tampil di bottom
- **Global Search**: Pencarian global
- **News Reader**: Notifikasi berita modern
- **View Transitions**: Smooth page transitions
- **Responsive Design**: Mobile-first dengan Mantine breakpoints
---
## 9. Komponen Utama
### Admin Components (`src/components/admin/`)
| Komponen | Deskripsi |
|----------|-----------|
| `AdminThemeProvider.tsx` | Theme provider untuk admin |
| `DarkModeToggle.tsx` | Toggle dark/light mode |
| `UnifiedSurface.tsx` | Consistent surface/card component |
| `UnifiedTypography.tsx` | Consistent typography system |
### Public Shared Components (`src/app/darmasaba/_com/`)
| Komponen | Deskripsi |
|----------|-----------|
| `Navbar.tsx` | Main navigation bar |
| `NavbarMainMenu.tsx` | Main menu dengan kategori |
| `NavbarSubMenu.tsx` | Submenu dropdown |
| `Footer.tsx` | Footer dengan info desa |
| `FixedPlayerBar.tsx` | Music player bar fixed di bottom |
| `LoadDataFirstClient.tsx` | Client-side data preloader |
| `globalSearch.tsx` | Global search component |
| `NewsReader.tsx` | News notification reader |
| `ModernNewsNotification.tsx` | News toast notifications |
### Global Components (`src/app/_com/`)
| Komponen | Deskripsi |
|----------|-----------|
| `SpashScreen.tsx` | Splash screen on load |
| `WebVitals.tsx` | Web Vitals monitoring |
---
## 10. State Management
Proyek menggunakan **multi-layer state management**:
| Library | Penggunaan | Lokasi |
|---------|-----------|--------|
| **Jotai** | Auth state (`authStore`) | `src/store/authStore.ts` |
| **Valtio** | Dark mode, layanan, image list, nav state | `src/state/*.ts` |
| **SWR** | Server state fetching & caching | Digunakan di components |
| **React Context** | Music player context | `src/app/context/MusicContext.tsx` |
| **React useState** | Local component state | Di components |
### State Files:
```
src/state/
darkModeStore.ts -- Valtio proxy untuk dark mode
state-layanan.ts -- State layanan desa
state-list-image.ts -- State list image untuk upload
state-nav.ts -- State navigasi
src/store/
authStore.ts -- Jotai atom untuk auth user state
```
---
## 11. Autentikasi
Sistem autentikasi menggunakan **OTP (One-Time Password)** via WhatsApp/Telepon dengan **iron-session** untuk session management.
### Flow Autentikasi:
1. User memasukkan **nomor telepon** di `/login`
2. Sistem mengirim **kode OTP** via WhatsApp Server
3. OTP disimpan di model `KodeOtp`
4. User memasukkan kode OTP
5. Jika valid, session dibuat dengan **iron-session** + **JWT token**
6. Session disimpan di `UserSession` model dengan expiry
### Session Structure:
```typescript
// src/lib/session.ts
type SessionData = {
user?: {
id: string;
name: string;
roleId: number;
menuIds?: string[] | null;
isActive?: boolean;
};
};
```
### Role-Based Access:
| roleId | Role | Default Redirect |
|--------|------|-----------------|
| 0, 1, 2 | Super Admin / Admin Desa | `/admin/landing-page/profil/program-inovasi` |
| 3 | Admin Kesehatan | `/admin/kesehatan/posyandu` |
| 4 | Admin Pendidikan | `/admin/pendidikan/info-sekolah/jenjang-pendidikan` |
### Authorization:
- **UserMenuAccess**: Mapping user ke menu yang boleh diakses
- **Dynamic Navbar**: Navbar dirender berdasarkan `menuIds` user
- **Inactive Users**: Dialihkan ke `/waiting-room`
---
## 12. Deployment
### Docker Setup
**Dockerfile** menggunakan **multi-stage build** dengan base image `oven/bun:1-debian`:
```
Stage 1: Builder
- Install dependencies (bun install --frozen-lockfile)
- Generate Prisma client
- Build Next.js (bun run build)
Stage 2: Runner
- Copy .next, node_modules, public, prisma, src/lib, tsconfig.json
- Non-root user (nextjs:nodejs)
- Volume /app/uploads untuk file uploads
- Port 3000
```
### Entry Point (`docker-entrypoint.sh`):
```bash
bunx prisma migrate deploy # Run migrations
exec bun start # Start Next.js production server
```
### CI/CD dengan GitHub Actions
Terdapat **3 workflow**:
| Workflow | Trigger | Fungsi |
|----------|---------|--------|
| `docker-publish.yml` | Push tag `v*` | Auto build & push ke GHCR |
| `publish.yml` | Manual (workflow_dispatch) | Build & push ke GHCR dengan input `stack_env` + `tag` |
| `re-pull.yml` | Manual (workflow_dispatch) | Re-pull image di Portainer dengan input `stack_name` + `stack_env` |
### Deployment Workflow (Sequential):
```
1. Update version di package.json (semver)
2. Commit perubahan
3. Push ke branch target (stg/prod)
4. Trigger publish.yml:
gh workflow run publish.yml --ref main -f stack_env=stg -f tag=<version>
5. Tunggu sampai publish selesai (status: completed)
6. Trigger re-pull.yml:
gh workflow run re-pull.yml --ref main -f stack_name=desa-darmasaba -f stack_env=stg
7. Verifikasi di Portainer
```
**PENTING**: `publish.yml` dan `re-pull.yml` TIDAK boleh dijalankan bersamaan (race condition).
### Environments:
- **dev**: Development
- **stg**: Staging (`desa-darmasaba-stg.wibudev.com`)
- **prod**: Production
### Notification:
- Telegram notification via `notify.sh` script setelah setiap workflow
---
## 13. Scripts
| Script | Command | Deskripsi |
|--------|---------|-----------|
| `dev` | `next dev` | Development server |
| `build` | `next build` | Production build |
| `start` | `next start` | Production server |
| `test:api` | `vitest run` | Run API unit tests |
| `test:e2e` | `playwright test` | Run E2E tests |
| `test` | `bun run test:api && bun run test:e2e` | Run all tests |
| `seed` | `bun run prisma/seed.ts` | Seed database |
| `prisma:generate` | `bunx prisma generate` | Generate Prisma client |
| `prisma:push` | `bunx prisma db push` | Push schema to database |
| `prisma:studio` | `bunx prisma studio` | Open Prisma Studio GUI |
| `gen:api` | *(empty)* | Generate API types (placeholder) |
### Prisma Seed Configuration:
```json
// package.json
{
"prisma": {
"seed": "bun run prisma/seed.ts"
}
}
```
---
## 14. Environment Variables
File: `.env.example`
| Variable | Deskripsi | Contoh |
|----------|-----------|--------|
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@localhost:5432/desa-darmasaba` |
| `SEAFILE_TOKEN` | Seafile API token | `your_seafile_token` |
| `SEAFILE_REPO_ID` | Seafile repository ID | `your_repo_id` |
| `SEAFILE_URL` | Seafile instance URL | `https://seafile.example.com` |
| `SEAFILE_PUBLIC_SHARE_TOKEN` | Token untuk public share | `your_share_token` |
| `WIBU_UPLOAD_DIR` | Upload directory path | `uploads` |
| `WA_SERVER_TOKEN` | WhatsApp server token | `your_wa_token` |
| `NEXT_PUBLIC_BASE_URL` | Base URL aplikasi | `/` (relative) |
| `EMAIL_USER` | Email untuk notifikasi | `your_email@gmail.com` |
| `EMAIL_PASS` | Email app password | `your_app_password` |
| `BASE_TOKEN_KEY` | JWT secret key | `your_jwt_secret` |
| `BOT_TOKEN` | Telegram bot token | `your_bot_token` |
| `CHAT_ID` | Telegram chat ID | `your_chat_id` |
| `SESSION_PASSWORD` | iron-session password (min 32 chars) | `secure_32_char_password` |
| `ELEVENLABS_API_KEY` | ElevenLabs API (TTS - optional) | `your_elevenlabs_key` |
---
## 15. Layanan Eksternal
### PostgreSQL
- **Provider**: PostgreSQL via Prisma ORM
- **Schema**: `public`
- **Connection**: Via `DATABASE_URL` environment variable
- **Migrations**: `prisma migrate deploy` di docker entrypoint
### Seafile (File Storage)
- **Tipe**: Self-hosted file sync & share
- **Penggunaan**: Storage untuk images, documents, audio files
- **Integrasi**: `src/lib/seafile-auth-service.ts`
- **CDN**: URL generation untuk public sharing
- **Config**: Token, repo ID, base URL
### WhatsApp Server
- **Penggunaan**: Kirim OTP codes saat login
- **Config**: `WA_SERVER_TOKEN`
### Telegram Bot
- **Penggunaan**: Notifikasi deployment & sistem
- **Config**: `BOT_TOKEN` + `CHAT_ID`
- **Integration**: `notify.sh` script di GitHub Actions
### ElevenLabs (Optional)
- **Penggunaan**: Text-to-Speech (TTS) features
- **Config**: `ELEVENLABS_API_KEY`
### Email (Nodemailer)
- **Penggunaan**: Notifikasi email untuk subscription/pengumuman
- **Config**: `EMAIL_USER` + `EMAIL_PASS`
- **Provider**: Gmail (app password)
---
## Ringkasan Cepat
| Aspek | Detail |
|-------|--------|
| **Framework** | Next.js 15 (App Router) + Elysia.js |
| **Database** | PostgreSQL + Prisma (100+ models) |
| **Auth** | OTP + iron-session + JWT |
| **Storage** | Seafile + local uploads |
| **UI** | Mantine UI + Tiptap + Framer Motion |
| **State** | Jotai + Valtio + SWR |
| **Deploy** | Docker + GHCR + Portainer + GitHub Actions |
| **Runtime** | Bun |
| **Testing** | Vitest + Playwright |
| **Version** | 0.1.11 |

49
biome.json Normal file
View 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"
}
}
}

133
bun.lock
View File

@@ -111,10 +111,11 @@
"@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",
"playwright-mcp": "^0.0.19",
"postcss": "^8.5.1",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
@@ -246,6 +247,8 @@
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
"@hono/node-server": ["@hono/node-server@1.19.13", "", { "peerDependencies": { "hono": "^4" } }, "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
@@ -360,6 +363,8 @@
"@mischnic/json-sourcemap": ["@mischnic/json-sourcemap@0.1.1", "", { "dependencies": { "@lezer/common": "^1.0.0", "@lezer/lr": "^1.0.0", "json5": "^2.2.1" } }, "sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
@@ -378,7 +383,7 @@
"@next/env": ["@next/env@15.5.14", "", {}, "sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.1.6", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-+slMxhTgILUntZDGNgsKEYHUvpn72WP1YTlkmEhS51vnVd7S9jEEy0n9YAMcI21vUG4akTw9voWH02lrClt/yw=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.5.12", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-+ZRSDFTv4aC96aMb5E41rMjysx8ApkryevnvEYZvPZO52KvkqP5rNExLUXJFr9P4s0f3oqNQR6vopCZsPWKDcQ=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg=="],
@@ -600,6 +605,8 @@
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
"@posthog/core": ["@posthog/core@1.25.1", "", {}, "sha512-76enEGwLVtcCTbfAr1wJ6zi4tYzVkGsqJx+H9JiX0K8/VxuKdbOtVShsOJhTD9uvFfTeW3DX+PSCelcqWrRRkw=="],
"@prisma/client": ["@prisma/client@6.3.1", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-ARAJaPs+eBkemdky/XU3cvGRl+mIPHCN2lCXsl5Vlb0E2gV+R6IN7aCI8CisRGszEZondwIsW9Iz8EJkTdykyA=="],
"@prisma/debug": ["@prisma/debug@6.3.1", "", {}, "sha512-RrEBkd+HLZx+ydfmYT0jUj7wjLiS95wfTOSQ+8FQbvb6vHh5AeKfEPt/XUQ5+Buljj8hltEfOslEW57/wQIVeA=="],
@@ -662,6 +669,8 @@
"@sinclair/typebox": ["@sinclair/typebox@0.34.49", "", {}, "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A=="],
"@skorotkiewicz/snowflake-id": ["@skorotkiewicz/snowflake-id@1.0.1", "", {}, "sha512-leZkiZoBgg6zpVfg+cMkehr/bdKXGa6pOoX0rNc1C2jVdGTWa87+/BBgIy8C4uVtI404cuOMBu2IzIXI1BE3TQ=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
@@ -926,6 +935,8 @@
"@vitest/utils": ["@vitest/utils@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
@@ -938,6 +949,8 @@
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"animate.css": ["animate.css@4.1.1", "", {}, "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -994,6 +1007,8 @@
"bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
"brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -1008,6 +1023,8 @@
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
@@ -1054,6 +1071,10 @@
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
@@ -1062,6 +1083,8 @@
"core-js": ["core-js@3.49.0", "", {}, "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg=="],
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
"crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
@@ -1132,6 +1155,8 @@
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
@@ -1154,6 +1179,8 @@
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="],
"elysia": ["elysia@1.4.28", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Vrx8sBnvq8squS/3yNBzR1jBXI+SgmnmvwawPjNuEHndUe5l1jV2Gp6JJ4ulDkEB8On6bWmmuyPpA+bq4t+WYg=="],
@@ -1168,6 +1195,8 @@
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
@@ -1196,11 +1225,13 @@
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
"eslint-config-next": ["eslint-config-next@15.1.6", "", { "dependencies": { "@next/eslint-plugin-next": "15.1.6", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-Wd1uy6y7nBbXUSg9QAuQ+xYEKli5CgUhLjz1QHW11jLDis5vK5XB3PemL6jEmy7HrdhaRFDz+GTZ/3FoH+EUjg=="],
"eslint-config-next": ["eslint-config-next@15.5.12", "", { "dependencies": { "@next/eslint-plugin-next": "15.5.12", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-ktW3XLfd+ztEltY5scJNjxjHwtKWk6vU2iwzZqSN09UsbBmMeE/cVlJ1yESg6Yx5LW7p/Z8WzUAgYXGLEmGIpg=="],
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="],
@@ -1232,8 +1263,14 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
"exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="],
"exif-js": ["exif-js@2.3.0", "", {}, "sha512-1Og9pAzG2FZRVlaavH8bB8BTeHcjMdJhKmeQITkX+uLRCD0xPtKAdZ2clZmQdJ56p9adXtJ8+jwrGp/4505lYg=="],
@@ -1242,6 +1279,10 @@
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
"express-rate-limit": ["express-rate-limit@8.3.2", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg=="],
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
@@ -1254,6 +1295,8 @@
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
@@ -1272,6 +1315,8 @@
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
"find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
@@ -1286,8 +1331,12 @@
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -1324,6 +1373,8 @@
"graphql": ["graphql@16.13.2", "", {}, "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig=="],
"happy-dom": ["happy-dom@17.6.3", "", { "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
@@ -1342,14 +1393,20 @@
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
"hono": ["hono@4.12.12", "", {}, "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q=="],
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
"html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -1362,10 +1419,16 @@
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"iron-session": ["iron-session@8.0.4", "", { "dependencies": { "cookie": "^0.7.2", "iron-webcrypto": "^1.2.1", "uncrypto": "^0.1.3" } }, "sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA=="],
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
@@ -1412,6 +1475,8 @@
"is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
@@ -1454,6 +1519,8 @@
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
@@ -1546,8 +1613,12 @@
"mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
@@ -1586,6 +1657,8 @@
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"next": ["next@15.5.14", "", { "dependencies": { "@next/env": "15.5.14", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.14", "@next/swc-darwin-x64": "15.5.14", "@next/swc-linux-arm64-gnu": "15.5.14", "@next/swc-linux-arm64-musl": "15.5.14", "@next/swc-linux-x64-gnu": "15.5.14", "@next/swc-linux-x64-musl": "15.5.14", "@next/swc-win32-arm64-msvc": "15.5.14", "@next/swc-win32-x64-msvc": "15.5.14", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ=="],
"next-view-transitions": ["next-view-transitions@0.3.5", "", { "peerDependencies": { "next": ">=14.0.0", "react": ">=18.2.0 || ^19.0.0", "react-dom": ">=18.2.0 || ^19.0.0" } }, "sha512-yP8OPNydLmKpmE94hLurLGEzPsUy1uyl9iSv8oxaC2JwhSXTD86SVwk1NMMQT7Ado4kMENDJ7fNBIXHy3GU/Lg=="],
@@ -1624,6 +1697,8 @@
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
@@ -1650,6 +1725,8 @@
"parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@@ -1668,10 +1745,14 @@
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
"playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="],
"playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="],
"playwright-mcp": ["playwright-mcp@0.0.19", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.25.1", "@skorotkiewicz/snowflake-id": "^1.0.1", "happy-dom": "^17.4.4", "lodash": "^4.17.21", "playwright": "^1.51.0", "posthog-node": "^5.11.1", "zod": "^3.24.2" }, "bin": { "playwright-mcp": "dist/server.js" } }, "sha512-KzqO66bShWvXQU7pGrb/NkQ3pQKRAxOK5H+tHVUCBv/0X+2fcV7AJrsgr97BOAo5dv+/7ZrNK6o7aMkMJ/MMmA=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
@@ -1690,6 +1771,8 @@
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"posthog-node": ["posthog-node@5.29.1", "", { "dependencies": { "@posthog/core": "1.25.1" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-XA1OGrE8SAmO3JkfWjeVg7XxEN/6M6ZXzYmBJKl3uXv/xxk7ru0oeoqi/rcWJ6z5+nFP9wUFVoS37//qaPxwFg=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"primeicons": ["primeicons@7.0.0", "", {}, "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="],
@@ -1736,6 +1819,8 @@
"prosemirror-view": ["prosemirror-view@1.41.8", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"proxy-compare": ["proxy-compare@3.0.1", "", {}, "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q=="],
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
@@ -1744,10 +1829,16 @@
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
"qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"ramda": ["ramda@0.27.2", "", {}, "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
@@ -1820,6 +1911,8 @@
"rope-sequence": ["rope-sequence@1.3.4", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="],
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
@@ -1830,18 +1923,26 @@
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -1940,6 +2041,8 @@
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
@@ -1958,6 +2061,8 @@
"type-fest": ["type-fest@5.5.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
"typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
@@ -1980,6 +2085,8 @@
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
"until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="],
@@ -2010,6 +2117,8 @@
"valtio": ["valtio@2.3.1", "", { "dependencies": { "proxy-compare": "^3.0.1" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-ZsOaOEn0U9IJ96cAj3CZ3GjwpN3EJdjsi1PT4PREuB+Pcqfsczu16isT5DT1UrmHbk4PtLjk8kwNEHuR2CX56w=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
"vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="],
@@ -2072,6 +2181,8 @@
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
"@cubejs-client/core/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@elysiajs/cookie/cookie": ["cookie@0.5.0", "", {}, "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="],
@@ -2084,6 +2195,8 @@
"@mantine/core/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"@modelcontextprotocol/sdk/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"@parcel/core/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
"@parcel/packager-js/globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
@@ -2102,6 +2215,8 @@
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
"ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"babel-plugin-macros/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
@@ -2120,10 +2235,16 @@
"eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"happy-dom/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
"happy-dom/whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
"iron-session/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"lmdb/node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="],
@@ -2146,10 +2267,14 @@
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
"string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
"@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@parcel/packager-js/globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
"@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
@@ -2158,6 +2283,8 @@
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"cross-fetch/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],

13
docker-entrypoint.sh Normal file
View 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

View File

@@ -11,6 +11,11 @@ const compat = new FlatCompat({
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
rules: {
"@typescript-eslint/no-explicit-any": "warn",
},
},
];
export default eslintConfig;

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.5",
"version": "0.1.12",
"private": true,
"scripts": {
"dev": "next dev",
@@ -125,10 +125,12 @@
"jsdom": "^28.0.0",
"msw": "^2.12.9",
"parcel": "^2.6.2",
"playwright-mcp": "^0.0.19",
"postcss": "^8.5.1",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
"typescript": "^5",
"vitest": "^4.0.18"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View 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");
}

View 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");
}

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import kategoriBerita from "../../../data/desa/berita/kategori-berita.json";
import beritaJson from "../../../data/desa/berita/berita.json";
import { loadJsonData } from "../../../load-json";
const kategoriBerita = loadJsonData("desa/berita/kategori-berita.json");
const beritaJson = loadJsonData("desa/berita/berita.json");
export async function seedBerita() {
// ================== SUBMENU BERITA ========================

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Foto...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Gallery Video...");

View File

@@ -1,8 +1,10 @@
import prisma from "@/lib/prisma";
import pelayananSuratKeterangan from "../../../data/desa/layanan/pelayananSuratKeterangan.json";
import pelayananTelunjukSaktiDesa from "../../../data/desa/layanan/pelayananTelunjukSaktiDesa.json";
import pelayananPerizinanBerusaha from "../../../data/desa/layanan/pelayananPerizinanBerusaha.json";
import pelayananPendudukNonPermanen from "../../../data/desa/layanan/pelayananPendudukNonPermanen.json";
import { loadJsonData } from "../../../load-json";
const pelayananSuratKeterangan = loadJsonData("desa/layanan/pelayananSuratKeterangan.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() {
console.log("🔄 Seeding Pelayanan Surat Keterangan...");

View 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");
}

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Penghargaan...");

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import { loadJsonData } from "../../../load-json";
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() {
console.log("🔄 Seeding Kategori Pengumuman...");

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import kategoriPotensi from "../../../data/desa/potensi/kategori-potensi.json";
import potensiDesa from "../../../data/desa/potensi/potensi-desa.json";
import { loadJsonData } from "../../../load-json";
const kategoriPotensi = loadJsonData("desa/potensi/kategori-potensi.json");
const potensiDesa = loadJsonData("desa/potensi/potensi-desa.json");
export async function seedPotensi() {
console.log("🔄Seeding Kategori Potensi Desa ...");

View File

@@ -1,10 +1,12 @@
import prisma from "@/lib/prisma";
import lambangDesa from "../../../data/desa/profile/lambang_desa.json";
import maskotDesa from "../../../data/desa/profile/maskot_desa.json";
import profilePerbekel from "../../../data/desa/profile/profil_perbekel.json";
import profileDesaImage from "../../../data/desa/profile/profileDesaImage.json";
import sejarahDesa from "../../../data/desa/profile/sejarah_desa.json";
import visiMisiDesa from "../../../data/desa/profile/visi_misi_desa.json";
import { loadJsonData } from "../../../load-json";
const lambangDesa = loadJsonData("desa/profile/lambang_desa.json");
const maskotDesa = loadJsonData("desa/profile/maskot_desa.json");
const profilePerbekel = loadJsonData("desa/profile/profil_perbekel.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() {
// =========== SEJARAH DESA ===========

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Perbekel Dari Masa Ke Masa...");

View 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");
}

View 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");
}

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Demografi Pekerjaan...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Jumlah Penduduk Miskin...");

View File

@@ -1,5 +1,7 @@
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() {
for (const d of jumlahPengangguran) {

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Lowongan Kerja Lokal...");

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import kategoriProduk from "../../data/ekonomi/pasar-desa/kategori-produk.json";
import pasarDesa from "../../data/ekonomi/pasar-desa/pasar-desa.json";
import kategoriToPasar from "../../data/ekonomi/pasar-desa/kategori-to-pasar.json";
import { loadJsonData } from "../../load-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() {
console.log("🔄 Seeding Kategori Produk...");

View File

@@ -1,8 +1,10 @@
import prisma from "@/lib/prisma";
import apbdes from "../../data/ekonomi/pendapatan-asli-desa/apbDesa.json";
import pendapatan from "../../data/ekonomi/pendapatan-asli-desa/pendapatanDesa.json";
import belanja from "../../data/ekonomi/pendapatan-asli-desa/belanjaDesa.json";
import pembiayaan from "../../data/ekonomi/pendapatan-asli-desa/pembiayaanDesa.json";
import { loadJsonData } from "../../load-json";
const apbdes = loadJsonData("ekonomi/pendapatan-asli-desa/apbDesa.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() {
console.log("🔄 Seeding Pendapatan Asli...");

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import grafikMenganggurBerdasarkanUsia from "../../data/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran-berdasarkan-usia.json";
import grafikMenganggurBerdasarkanPendidikan from "../../data/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran-berdasarkan-pendidikan.json";
import { loadJsonData } from "../../load-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() {
for (const p of grafikMenganggurBerdasarkanUsia) {

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import programKemiskinan from "../../data/ekonomi/program-kemiskinan/program-kemiskinan.json";
import statistikKemiskinan from "../../data/ekonomi/program-kemiskinan/statistik-kemiskinan.json";
import { loadJsonData } from "../../load-json";
const programKemiskinan = loadJsonData("ekonomi/program-kemiskinan/program-kemiskinan.json");
const statistikKemiskinan = loadJsonData("ekonomi/program-kemiskinan/statistik-kemiskinan.json");
export async function seedProgramKemiskinan() {
for (const s of statistikKemiskinan) {

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Sektor Unggulan Desa...");

View File

@@ -1,6 +1,28 @@
import prisma from "@/lib/prisma";
import posisiOrganisasiBumDes from "../../data/ekonomi/struktur-organisasi/posisi-organisasi-bumdes.json";
import pegawai from "../../data/ekonomi/struktur-organisasi/pegawai-bumdes.json";
import { loadJsonData } from "../../load-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() {
const flattenedPosisi = posisiOrganisasiBumDes.flat();

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Ajukan Ide Inovatif...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Desa Digital...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Info Teknologi...");

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import kolaborasiInovasi from "../../data/inovasi/kolaborasi-inovasi/kolaborasi-inovasi.json";
import mitraKolaborasi from "../../data/inovasi/kolaborasi-inovasi/mitra-kolaborasi.json";
import { loadJsonData } from "../../load-json";
const kolaborasiInovasi = loadJsonData("inovasi/kolaborasi-inovasi/kolaborasi-inovasi.json");
const mitraKolaborasi = loadJsonData("inovasi/kolaborasi-inovasi/mitra-kolaborasi.json");
export async function seedKolaborasiInovasi() {
console.log("🔄 Seeding Kolaborasi Inovasi...");

View File

@@ -1,8 +1,10 @@
import prisma from "@/lib/prisma";
import jenisLayanan from "../../data/inovasi/layanan-online-desa/jenis-layanan.json";
import administrasiOnline from "../../data/inovasi/layanan-online-desa/administrasi-online.json";
import jenisPengaduan from "../../data/inovasi/layanan-online-desa/jenis-pengaduan.json";
import pengaduanMasyarakat from "../../data/inovasi/layanan-online-desa/pengaduan-masyarakat.json";
import { loadJsonData } from "../../load-json";
const jenisLayanan = loadJsonData("inovasi/layanan-online-desa/jenis-layanan.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() {
console.log("🔄 Seeding Jenis Layanan...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Program Kreatif...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Keamanan Lingkungan...");

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import kontakDaruratKeamanan from "../../data/keamanan/kontak-darurat-keamanan/kontak-darurat-keamanan.json";
import kontakItem from "../../data/keamanan/kontak-darurat-keamanan/kontakItem.json";
import kontakDaruratToItem from "../../data/keamanan/kontak-darurat-keamanan/kontakDaruratToItem.json";
import { loadJsonData } from "../../load-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() {
console.log("🔄 Seeding Kontak Item...");

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import laporanPublik from "../../data/keamanan/laporan-publik/laporan-publik.json";
import penangananLaporan from "../../data/keamanan/laporan-publik/penanganan-laporan.json";
import { loadJsonData } from "../../load-json";
const laporanPublik = loadJsonData("keamanan/laporan-publik/laporan-publik.json");
const penangananLaporan = loadJsonData("keamanan/laporan-publik/penanganan-laporan.json");
export async function seedLaporanPublik() {
console.log("🔄 Seeding Laporan Publik...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Pencegahan Kriminalitas...");

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import layananPolsek from "../../data/keamanan/polsek-terdekat/layanan-polsek.json";
import polsekTerdekat from "../../data/keamanan/polsek-terdekat/polsek-terdekat.json";
import layananToPolsek from "../../data/keamanan/polsek-terdekat/layanan-to-polsek.json";
import { loadJsonData } from "../../load-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() {
console.log("🔄 Seeding Layanan Polsek...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Tips Keamanan...");

View 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");
}

View 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");
}

View 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");
}

View 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");
}

View 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");
}

View File

@@ -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");
}

View File

@@ -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");
}

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Info Wabah Penyakit...");

View File

@@ -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");
}

View File

@@ -1,6 +1,8 @@
import kontakDaruratJson from "../../../data/kesehatan/kontak-darurat/kontak-darurat.json";
import { loadJsonData } from "../../../load-json";
import prisma from "@/lib/prisma";
const kontakDaruratJson = loadJsonData("kesehatan/kontak-darurat/kontak-darurat.json");
export async function seedKontakDarurat() {
console.log("🔄 Seeding Kontak Darurat...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Penanganan Darurat...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Posyandu...");

View File

@@ -1,5 +1,7 @@
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() {
for (const p of programKesehatanJson) {

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import puskesmasJson from "../../../data/kesehatan/puskesmas/puskesmas.json";
import kontakPuskesmasJson from "../../../data/kesehatan/puskesmas/kontak-puskesmas/kontak.json";
import jamPuskesmasJson from "../../../data/kesehatan/puskesmas/jam-puskesmas/jam.json";
import { loadJsonData } from "../../../load-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() {
console.log("🔄 Seeding Kontak Puskesmas...");

View 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");
}

View 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");
}

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import kategoriDesaAntiKorupsi from "../../../data/landing-page/desa-anti-korupsi/kategoriDesaAntiKorupsi.json"
import desaAntiKorupsi from "../../../data/landing-page/desa-anti-korupsi/desaantiKorpusi.json"
import { loadJsonData } from "../../../load-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() {
for (const k of kategoriDesaAntiKorupsi) {

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import prestasiDesa from "../../../data/landing-page/prestasi-desa/prestasi-desa.json"
import kategoriPrestasiDesa from "../../../data/landing-page/prestasi-desa/kategori-prestasi.json"
import { loadJsonData } from "../../../load-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() {

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Media Sosial...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Pejabat Desa...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Program Inovasi...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding SDGS Desa...");

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import kategoriGotongRoyong from "../../data/lingkungan/gotong-royong/kategori-gotong-royong.json";
import gotongRoyong from "../../data/lingkungan/gotong-royong/gotong-royong.json";
import { loadJsonData } from "../../load-json";
const kategoriGotongRoyong = loadJsonData("lingkungan/gotong-royong/kategori-gotong-royong.json");
const gotongRoyong = loadJsonData("lingkungan/gotong-royong/gotong-royong.json");
export async function seedDataGotongRoyong() {
console.log("🔄 Seeding Kategori Gotong Royong...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Data Lingkungan Desa...");

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import tujuanEdukasiLingkungan from "../../data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.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";
import { loadJsonData } from "../../load-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() {
for (const e of tujuanEdukasiLingkungan) {

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import filosofiTriHita from "../../data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json";
import bentukKonservasiBerdasarkanAdat from "../../data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
import nilaiKonservasiAdat from "../../data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
import { loadJsonData } from "../../load-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() {
for (const f of filosofiTriHita) {

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import pengelolaanSampah from "../../data/lingkungan/pengelolaan-sampah/pengelolaan-sampah.json";
import keteranganBankSampah from "../../data/lingkungan/pengelolaan-sampah/keterangan-bank-sampah.json";
import { loadJsonData } from "../../load-json";
const pengelolaanSampah = loadJsonData("lingkungan/pengelolaan-sampah/pengelolaan-sampah.json");
const keteranganBankSampah = loadJsonData("lingkungan/pengelolaan-sampah/keterangan-bank-sampah.json");
export async function seedPengelolaanSampah() {
console.log("🔄 Seeding Pengelolaan Sampah...");

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Program Penghijauan...");

View 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");
}

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import tujuanBimbinganBelajarDesa from "../../data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.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";
import { loadJsonData } from "../../load-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() {
for (const t of tujuanBimbinganBelajarDesa) {

View File

@@ -1,5 +1,7 @@
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() {
console.log("🔄 Seeding Data pendidikan...");

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import dataPerpustakaan from "../../data/pendidikan/perpustakaan-digital/perpustakaan-digital.json";
import kategoriBuku from "../../data/pendidikan/perpustakaan-digital/kategori-buku.json";
import { loadJsonData } from "../../load-json";
const dataPerpustakaan = loadJsonData("pendidikan/perpustakaan-digital/perpustakaan-digital.json");
const kategoriBuku = loadJsonData("pendidikan/perpustakaan-digital/kategori-buku.json");
export async function seedDataPerpustakaan() {
console.log("🔄 Seeding Kategori Buku...");

View File

@@ -1,6 +1,8 @@
import prisma from "@/lib/prisma";
import tujuanProgram from "../../data/pendidikan/program-pendidikan-anak/tujuan-program.json";
import programUnggulan from "../../data/pendidikan/program-pendidikan-anak/program-unggulan.json";
import { loadJsonData } from "../../load-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() {
for (const t of tujuanProgram) {

View File

@@ -1,8 +1,10 @@
import prisma from "@/lib/prisma";
import jenjangPendidikan from "../../data/pendidikan/info-sekolah/jenjang-pendidikan.json";
import lembagaPendidikan from "../../data/pendidikan/info-sekolah/lembaga.json";
import siswa from "../../data/pendidikan/info-sekolah/siswa.json";
import pengajar from "../../data/pendidikan/info-sekolah/pengajar.json";
import { loadJsonData } from "../../load-json";
const jenjangPendidikan = loadJsonData("pendidikan/info-sekolah/jenjang-pendidikan.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() {
for (const j of jenjangPendidikan) {

View 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");
}

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import tujuanProgram from "../../data/pendidikan/pendidikan-non-formal/tujuan-program2.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";
import { loadJsonData } from "../../load-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() {
for (const t of tujuanProgram) {

View File

@@ -1,8 +1,10 @@
import prisma from "@/lib/prisma";
import daftarInformasiPublik from "../../../data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json"
import jenisInformasiDiminta from "../../../data/list-jenisInfromasi.json"
import caraMemperolehInformasi from "../../../data/list-caraMemperolehInformasi.json"
import caraMemperolehSalinanInformasi from "../../../data/list-caraMemperolehSalinanInformasi.json"
import { loadJsonData } from "../../../load-json";
const daftarInformasiPublik = loadJsonData("ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json");
const jenisInformasiDiminta = loadJsonData("list-jenisInfromasi.json");
const caraMemperolehInformasi = loadJsonData("list-caraMemperolehInformasi.json");
const caraMemperolehSalinanInformasi = loadJsonData("list-caraMemperolehSalinanInformasi.json");
export async function seedDaftarInformasiPublikPpid() {

View File

@@ -1,5 +1,7 @@
import prisma from "@/lib/prisma";
import dasarHukumPPID from "../../../data/ppid/dasar-hukum-ppid/dasarhukumPPID.json"
import { loadJsonData } from "../../../load-json";
const dasarHukumPPID = loadJsonData("ppid/dasar-hukum-ppid/dasarhukumPPID.json");
export async function seedDasarHukumPpid() {
for (const v of dasarHukumPPID) {

View File

@@ -1,7 +1,9 @@
import prisma from "@/lib/prisma";
import jenisKelamin from "../../../data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
import pilihanRatingResponden from "../../../data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
import umurResponden from "../../../data/ppid/ikm/umur-responden/umur-responden.json";
import { loadJsonData } from "../../../load-json";
const jenisKelamin = loadJsonData("ppid/ikm/jenis-kelamin/jenis-kelamin.json");
const pilihanRatingResponden = loadJsonData("ppid/ikm/pilihan-rating-responden/rating-responden.json");
const umurResponden = loadJsonData("ppid/ikm/umur-responden/umur-responden.json");
export async function seedIkmPpid() {
for (const j of jenisKelamin) {

View File

@@ -1,5 +1,7 @@
import prisma from "@/lib/prisma";
import profilPpd from "../../../data/ppid/profile-ppid/profilePPid.json"
import { loadJsonData } from "../../../load-json";
const profilPpd = loadJsonData("ppid/profile-ppid/profilePPid.json");
export async function seedProfilPpd() {
console.log("🔄 Seeding Profil PPD...");

View File

@@ -0,0 +1,161 @@
import prisma from "@/lib/prisma";
import { loadJsonData } from "../../load-json";
const strukturOrganisasiPpidJson = loadJsonData("ppid/struktur-organisasi-ppid/struktur-organisasi-ppid.json");
const formulirPermohonanKeberatanJson = loadJsonData("ppid/formulir-permohonan-keberatan/formulir-permohonan-keberatan.json");
const ikmJson = loadJsonData("ppid/ikm/indeks-kepuasan-masyarakat/indeks-kepuasan-masyarakat.json");
const grafikJenisKelaminJson = loadJsonData("ppid/ikm/grafik-jenis-kelamin/grafik-jenis-kelamin.json");
const grafikRespondenJson = loadJsonData("ppid/ikm/grafik-responden/grafik-responden.json");
const grafikUmurJson = loadJsonData("ppid/ikm/grafik-umur/grafik-umur.json");
export async function seedStrukturOrganisasiPpid() {
console.log("🔄 Seeding StrukturOrganisasiPPID...");
for (const item of strukturOrganisasiPpidJson) {
await prisma.strukturOrganisasiPPID.upsert({
where: { id: item.id },
update: {
posisiOrganisasiId: item.posisiOrganisasiId,
pegawaiId: item.pegawaiId,
hubunganOrganisasiId: item.hubunganOrganisasiId,
},
create: {
id: item.id,
posisiOrganisasiId: item.posisiOrganisasiId,
pegawaiId: item.pegawaiId,
hubunganOrganisasiId: item.hubunganOrganisasiId,
},
});
console.log(`✅ StrukturOrganisasiPPID seeded: ${item.id}`);
}
console.log("🎉 StrukturOrganisasiPPID seed selesai");
}
export async function seedFormulirPermohonanKeberatan() {
console.log("🔄 Seeding FormulirPermohonanKeberatan...");
for (const item of formulirPermohonanKeberatanJson) {
await prisma.formulirPermohonanKeberatan.upsert({
where: { id: item.id },
update: {
name: item.name,
email: item.email,
notelp: item.notelp,
alasan: item.alasan,
},
create: {
id: item.id,
name: item.name,
email: item.email,
notelp: item.notelp,
alasan: item.alasan,
},
});
console.log(`✅ FormulirPermohonanKeberatan seeded: ${item.name}`);
}
console.log("🎉 FormulirPermohonanKeberatan seed selesai");
}
export async function seedIndeksKepuasanMasyarakat() {
console.log("🔄 Seeding IndeksKepuasanMasyarakat...");
for (const item of ikmJson) {
await prisma.indeksKepuasanMasyarakat.upsert({
where: { id: item.id },
update: {
label: item.label,
kepuasan: item.kepuasan,
},
create: {
id: item.id,
label: item.label,
kepuasan: item.kepuasan,
},
});
console.log(`✅ IndeksKepuasanMasyarakat seeded: ${item.label}`);
}
console.log("🎉 IndeksKepuasanMasyarakat seed selesai");
}
export async function seedGrafikBerdasarkanJenisKelamin() {
console.log("🔄 Seeding GrafikBerdasarkanJenisKelamin...");
for (const item of grafikJenisKelaminJson) {
await prisma.grafikBerdasarkanJenisKelamin.upsert({
where: { id: item.id },
update: {
perempuan: item.perempuan,
laki: item.laki,
},
create: {
id: item.id,
perempuan: item.perempuan,
laki: item.laki,
},
});
console.log(`✅ GrafikBerdasarkanJenisKelamin seeded: ${item.id}`);
}
console.log("🎉 GrafikBerdasarkanJenisKelamin seed selesai");
}
export async function seedGrafikBerdasarkanResponden() {
console.log("🔄 Seeding GrafikBerdasarkanResponden...");
for (const item of grafikRespondenJson) {
await prisma.grafikBerdasarkanResponden.upsert({
where: { id: item.id },
update: {
sangatbaik: item.sangatbaik,
baik: item.baik,
kurangbaik: item.kurangbaik,
tidakbaik: item.tidakbaik,
},
create: {
id: item.id,
sangatbaik: item.sangatbaik,
baik: item.baik,
kurangbaik: item.kurangbaik,
tidakbaik: item.tidakbaik,
},
});
console.log(`✅ GrafikBerdasarkanResponden seeded: ${item.id}`);
}
console.log("🎉 GrafikBerdasarkanResponden seed selesai");
}
export async function seedGrafikBerdasarkanUmur() {
console.log("🔄 Seeding GrafikBerdasarkanUmur...");
for (const item of grafikUmurJson) {
await prisma.grafikBerdasarkanUmur.upsert({
where: { id: item.id },
update: {
remaja: item.remaja,
dewasa: item.dewasa,
orangtua: item.orangtua,
lansia: item.lansia,
},
create: {
id: item.id,
remaja: item.remaja,
dewasa: item.dewasa,
orangtua: item.orangtua,
lansia: item.lansia,
},
});
console.log(`✅ GrafikBerdasarkanUmur seeded: ${item.id}`);
}
console.log("🎉 GrafikBerdasarkanUmur seed selesai");
}

View File

@@ -1,6 +1,29 @@
import prisma from "@/lib/prisma";
import pegawaiPpid from "../../../data/ppid/struktur-ppid/pegawai-PPID.json"
import posisiOrganisasiPPID from "../../../data/ppid/struktur-ppid/posisi-organisasi-PPID.json"
import { loadJsonData } from "../../../load-json";
interface PegawaiPPID {
id: string;
namaLengkap: string;
gelarAkademik: string;
tanggalMasuk: string;
email: string;
telepon: string;
alamat: string;
imageName?: string;
posisiId: string;
isActive: boolean;
}
interface PosisiOrganisasiPPID {
id: string;
nama: string;
deskripsi: string;
hierarki: number;
parentId: string | null;
}
const pegawaiPpid = loadJsonData<PegawaiPPID[]>("ppid/struktur-ppid/pegawai-PPID.json");
const posisiOrganisasiPPID = loadJsonData<PosisiOrganisasiPPID[][]>("ppid/struktur-ppid/posisi-organisasi-PPID.json");
export async function seedPegawaiPpid() {

View File

@@ -1,5 +1,7 @@
import prisma from "@/lib/prisma";
import visiMisiPPID from "../../../data/ppid/visi-misi-ppid/visimisiPPID.json"
import { loadJsonData } from "../../../load-json";
const visiMisiPPID = loadJsonData("ppid/visi-misi-ppid/visimisiPPID.json");
export async function seedVisiMisiPpid() {
for (const v of visiMisiPPID) {

View File

@@ -0,0 +1,44 @@
[
{
"id": "menu-child-001",
"name": "Sejarah Desa",
"link": "/profil-desa/sejarah",
"isActive": true,
"appMenuId": "menu-002"
},
{
"id": "menu-child-002",
"name": "Visi Misi",
"link": "/profil-desa/visi-misi",
"isActive": true,
"appMenuId": "menu-002"
},
{
"id": "menu-child-003",
"name": "Surat Keterangan",
"link": "/layanan/surat-keterangan",
"isActive": true,
"appMenuId": "menu-003"
},
{
"id": "menu-child-004",
"name": "Perizinan",
"link": "/layanan/perizinan",
"isActive": true,
"appMenuId": "menu-003"
},
{
"id": "menu-child-005",
"name": "Berita Desa",
"link": "/informasi/berita",
"isActive": true,
"appMenuId": "menu-004"
},
{
"id": "menu-child-006",
"name": "Pengumuman",
"link": "/informasi/pengumuman",
"isActive": true,
"appMenuId": "menu-004"
}
]

View File

@@ -0,0 +1,32 @@
[
{
"id": "menu-001",
"name": "Beranda",
"link": "/",
"isActive": true
},
{
"id": "menu-002",
"name": "Profil Desa",
"link": "/profil-desa",
"isActive": true
},
{
"id": "menu-003",
"name": "Layanan",
"link": "/layanan",
"isActive": true
},
{
"id": "menu-004",
"name": "Informasi",
"link": "/informasi",
"isActive": true
},
{
"id": "menu-005",
"name": "Kontak",
"link": "/kontak",
"isActive": true
}
]

View File

@@ -0,0 +1,6 @@
[
{
"id": "lp-layanan-001",
"deksripsi": "Melayani masyarakat Desa Darmasaba dengan sepenuh hati untuk memberikan pelayanan terbaik dalam bidang administrasi, kesehatan, pendidikan, dan keamanan."
}
]

View File

@@ -0,0 +1,22 @@
[
{
"id": "layanan-001",
"name": "Administrasi"
},
{
"id": "layanan-002",
"name": "Kesehatan"
},
{
"id": "layanan-003",
"name": "Pendidikan"
},
{
"id": "layanan-004",
"name": "Keamanan"
},
{
"id": "layanan-005",
"name": "Lingkungan"
}
]

View File

@@ -0,0 +1,22 @@
[
{
"id": "potensi-001",
"name": "Pertanian"
},
{
"id": "potensi-002",
"name": "Peternakan"
},
{
"id": "potensi-003",
"name": "Perikanan"
},
{
"id": "potensi-004",
"name": "Kerajinan"
},
{
"id": "potensi-005",
"name": "Pariwisata"
}
]

View File

@@ -0,0 +1,41 @@
[
{
"id": "cmk-musik-001",
"judul": "Darmasaba Swara",
"artis": "Gamelan Desa Darmasaba",
"deskripsi": "Lagu tradisional Bali yang dibawakan oleh grup gamelan desa Darmasaba. Lagu ini menceritakan keindahan alam dan kearifan lokal desa.",
"durasi": "04:32",
"audioFileId": null,
"audioFileName": null,
"coverImageId": null,
"coverImageName": "TDQReg1lQ73s39crXW0ra-mobile.webp",
"genre": "Gamelan Tradisional",
"tahunRilis": 2024
},
{
"id": "cmk-musik-002",
"judul": "Mekar di Badung",
"artis": "Kadek Wira & Darmasaba Band",
"deskripsi": "Lagu pop Bali modern yang mengangkat tema semangat pemuda desa dalam membangun kampung halaman.",
"durasi": "03:45",
"audioFileId": null,
"audioFileName": null,
"coverImageId": null,
"coverImageName": "EcQIGOF6LW1dIKE53vmba-mobile.webp",
"genre": "Pop Bali",
"tahunRilis": 2025
},
{
"id": "cmk-musik-003",
"judul": "Tri Hita Karana",
"artis": "Sekaa Gong Darmasaba",
"deskripsi": "Komposisi gamelan yang terinspirasi dari filosofi Tri Hita Karana - tiga penyebab kebahagiaan dalam kehidupan masyarakat Bali.",
"durasi": "05:18",
"audioFileId": null,
"audioFileName": null,
"coverImageId": null,
"coverImageName": "Gc79mlIlGuoRQuTqskFj--mobile.webp",
"genre": "Gamelan Kontemporer",
"tahunRilis": 2024
}
]

Some files were not shown because too many files have changed in this diff Show More