tambahannya

This commit is contained in:
bipproduction
2025-10-15 21:17:25 +08:00
commit 9e60f5ebf6
80 changed files with 17816 additions and 0 deletions

4
.env.example Normal file
View File

@@ -0,0 +1,4 @@
DATABASE_URL="postgresql://user:pasword@localhost:5432/wajs?schema=public"
JWT_SECRET=super_sangat_rahasia_sekali
BUN_PUBLIC_BASE_URL=http://localhost:3000
PORT=3000

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

90
README.md Normal file
View File

@@ -0,0 +1,90 @@
# Full-Stack Template: Bun, ElysiaJS, React, and Prisma
This is a comprehensive full-stack application template built with a modern JavaScript toolchain. It provides a solid foundation for building fast, type-safe web applications.
## ✨ Features & Tech Stack
- **Runtime & Bundler**: [Bun](https://bun.sh/) - An incredibly fast all-in-one JavaScript runtime.
- **Backend Framework**: [ElysiaJS](https://elysiajs.com/) - A fast, ergonomic, and type-safe backend framework for Bun.
- **Frontend Library**: [React](https://react.dev/) - A popular library for building user interfaces.
- **UI Components**: [Mantine](https://mantine.dev/) - A full-featured React component library.
- **Database ORM**: [Prisma](https://www.prisma.io/) - A next-generation Node.js and TypeScript ORM.
- **Type-Safe Client**: [Eden](https://elysiajs.com/plugins/eden.html) - Creates a type-safe client from your ElysiaJS API to be used in the frontend.
- **API Documentation**: [Swagger](https://elysiajs.com/plugins/swagger.html) - Automatically generated API documentation.
- **Authentication**: JWT-based authentication middleware is included.
## 🚀 Getting Started
### 1. Prerequisites
Ensure you have [Bun](https://bun.sh/docs/installation) installed on your system.
### 2. Clone the Repository
```bash
git clone <repository-url>
cd bun-template
```
### 3. Install Dependencies
```bash
bun install
```
### 4. Set Up Environment Variables
Copy the example environment file and update it with your own configuration.
```bash
cp .env.example .env
```
You will need to configure the following variables in the `.env` file:
- `DATABASE_URL`: Your PostgreSQL connection string.
- `JWT_SECRET`: A secret key for signing JWTs.
- `BUN_PUBLIC_BASE_URL`: The public base URL of your application (e.g., `http://localhost:3000`).
- `PORT`: The port the server will run on.
### 5. Set Up the Database
Run the Prisma migration to create the database schema. This will also apply any pending migrations.
```bash
bunx prisma migrate dev
```
### 6. Seed the Database (Optional)
You can seed the database with initial data using the provided seed script.
```bash
bunx prisma db seed
```
## 📜 Available Scripts
- **`bun dev`**: Starts the development server for both the backend and frontend with hot-reloading. The server runs on the port specified in your `.env` file (defaults to 3000).
- **`bun build`**: Builds the React frontend for production. The output is placed in the `dist/` directory.
- **`bun start`**: Starts the application in production mode. Make sure you have run `bun build` first.
## 📂 Project Structure
```
.
├── prisma/ # Prisma schema, migrations, and seed script
├── src/
│ ├── components/ # Shared React components
│ ├── lib/ # Shared library functions (e.g., apiFetch)
│ ├── pages/ # React components for different pages/routes
│ ├── server/ # ElysiaJS backend code (routes, middlewares)
│ ├── App.tsx # Main React App component
│ ├── frontend.tsx # Frontend entry point (client-side)
│ ├── index.css # Global styles
│ ├── index.html # HTML template for the frontend
│ └── index.tsx # Main application entry point (server-side)
└── ...
```

17
bun-env.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
// Generated by `bun init`
declare module "*.svg" {
/**
* A path to the SVG file
*/
const path: `${string}.svg`;
export = path;
}
declare module "*.module.css" {
/**
* A record of class names to their corresponding CSS module classes
*/
const classes: { readonly [key: string]: string };
export = classes;
}

571
bun.lock Normal file
View File

@@ -0,0 +1,571 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "bun-react-template",
"dependencies": {
"@elysiajs/cors": "^1.4.0",
"@elysiajs/eden": "^1.4.4",
"@elysiajs/jwt": "^1.4.0",
"@elysiajs/swagger": "^1.3.1",
"@lglab/react-qr-code": "^1.4.5",
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/modals": "^8.3.5",
"@mantine/notifications": "^8.3.4",
"@monaco-editor/react": "^4.7.0",
"@prisma/client": "^6.17.1",
"@tabler/icons-react": "^3.35.0",
"@types/jwt-decode": "^3.1.0",
"@types/lodash": "^4.17.20",
"@types/qrcode-terminal": "^0.12.2",
"add": "^2.0.6",
"elysia": "^1.4.11",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"qrcode-terminal": "^0.12.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-qr-code": "^2.0.18",
"react-router-dom": "^7.9.4",
"swr": "^2.3.6",
"uuid": "^13.0.0",
"whatsapp-web.js": "^1.34.1",
},
"devDependencies": {
"@types/bun": "latest",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"postcss": "^8.5.6",
"postcss-preset-mantine": "^1.18.0",
"postcss-simple-vars": "^7.0.1",
"prisma": "^6.17.1",
},
},
},
"packages": {
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
"@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
"@elysiajs/cors": ["@elysiajs/cors@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-pb0SCzBfFbFSYA/U40HHO7R+YrcXBJXOWgL20eSViK33ol1e20ru2/KUaZYo5IMUn63yaTJI/bQERuQ+77ND8g=="],
"@elysiajs/eden": ["@elysiajs/eden@1.4.4", "", { "peerDependencies": { "elysia": ">= 1.4.0-exp.0" } }, "sha512-/LVqflmgUcCiXb8rz1iRq9Rx3SWfIV/EkoNqDFGMx+TvOyo8QHAygFXAVQz7RHs+jk6n6mEgpI6KlKBANoErsQ=="],
"@elysiajs/jwt": ["@elysiajs/jwt@1.4.0", "", { "dependencies": { "jose": "^6.0.11" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Z0PvZhQxdDeKZ8HslXzDoXXD83NKExNPmoiAPki3nI2Xvh5wtUrBH+zWOD17yP14IbRo8fxGj3L25MRCAPsgPA=="],
"@elysiajs/swagger": ["@elysiajs/swagger@1.3.1", "", { "dependencies": { "@scalar/themes": "^0.9.52", "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-LcbLHa0zE6FJKWPWKsIC/f+62wbDv3aXydqcNPVPyqNcaUgwvCajIi+5kHEU6GO3oXUCpzKaMsb3gsjt8sLzFQ=="],
"@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
"@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
"@floating-ui/react": ["@floating-ui/react@0.27.16", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g=="],
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.6", "", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="],
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
"@lglab/react-qr-code": ["@lglab/react-qr-code@1.4.5", "", { "peerDependencies": { "react": "^18 || ^19" } }, "sha512-kfaWOsbqqN+iskfRJLSHaPCHX0FAnRtZefj86rPq8WAZUVe9fnRzpr/xlOlXSsC+g1KWdm6LgXeoKgkvOa2F7g=="],
"@mantine/core": ["@mantine/core@8.3.4", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "clsx": "^2.1.1", "react-number-format": "^5.4.4", "react-remove-scroll": "^2.7.1", "react-textarea-autosize": "8.5.9", "type-fest": "^4.41.0" }, "peerDependencies": { "@mantine/hooks": "8.3.4", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-RJ5QUe2FLLJ1uF8xWUpNhDqRFbaOn4S5yTjqLuaurqtZvzee85O/T90dRcR8UNDuE8e/Qqie/jsF/G9RiSxC6g=="],
"@mantine/hooks": ["@mantine/hooks@8.3.4", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-zsgFa9yzJ4tz0BZmI+yAjI3uNEe6SoQHZlXmvDNXuWrWImpl6Zhoxxr/oVaISB4+KZJd3pRPajklQ+CETrMdhg=="],
"@mantine/modals": ["@mantine/modals@8.3.5", "", { "peerDependencies": { "@mantine/core": "8.3.5", "@mantine/hooks": "8.3.5", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-8pEhVc2NqUcO1+mQab1J5hDwMGKbqwMWMQptF++PUI0e82BGyoxuOdYywWvvW7+UzcA1REMF7uy0mfG9RLcjew=="],
"@mantine/notifications": ["@mantine/notifications@8.3.4", "", { "dependencies": { "@mantine/store": "8.3.4", "react-transition-group": "4.4.5" }, "peerDependencies": { "@mantine/core": "8.3.4", "@mantine/hooks": "8.3.4", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-4qgvHYCTdfUVBI0FGEL/1NbmMFlmdYHk0ATuyOjJbmpw+y6OnA8FsCqfu0PHagntm+YmWJJwJRwq7TQMbULTaA=="],
"@mantine/store": ["@mantine/store@8.3.4", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-EzHu8ZPtjs4fUsougpnNOZ03Oe/9sXY9Azgqu3uR7TjxyPhbY3p8Vmh+MfQDdAiKt3+rlJqSr980gtlHJFdKNw=="],
"@monaco-editor/loader": ["@monaco-editor/loader@1.6.1", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-w3tEnj9HYEC73wtjdpR089AqkUPskFRcdkxsiSFt3SoUc3OHpmu+leP94CXBm4mHfefmhsdfI0ZQu6qJ0wgtPg=="],
"@monaco-editor/react": ["@monaco-editor/react@4.7.0", "", { "dependencies": { "@monaco-editor/loader": "^1.5.0" }, "peerDependencies": { "monaco-editor": ">= 0.25.0 < 1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA=="],
"@pedroslopez/moduleraid": ["@pedroslopez/moduleraid@5.0.2", "", {}, "sha512-wtnBAETBVYZ9GvcbgdswRVSLkFkYAGv1KzwBBTeRXvGT9sb9cPllOgFFWXCn9PyARQ0H+Ijz6mmoRrGateUDxQ=="],
"@prisma/client": ["@prisma/client@6.17.1", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw=="],
"@prisma/config": ["@prisma/config@6.17.1", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.16.12", "empathic": "2.0.0" } }, "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA=="],
"@prisma/debug": ["@prisma/debug@6.17.1", "", {}, "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ=="],
"@prisma/engines": ["@prisma/engines@6.17.1", "", { "dependencies": { "@prisma/debug": "6.17.1", "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", "@prisma/fetch-engine": "6.17.1", "@prisma/get-platform": "6.17.1" } }, "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw=="],
"@prisma/engines-version": ["@prisma/engines-version@6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", "", {}, "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg=="],
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.17.1", "", { "dependencies": { "@prisma/debug": "6.17.1", "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", "@prisma/get-platform": "6.17.1" } }, "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg=="],
"@prisma/get-platform": ["@prisma/get-platform@6.17.1", "", { "dependencies": { "@prisma/debug": "6.17.1" } }, "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg=="],
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="],
"@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="],
"@scalar/types": ["@scalar/types@0.0.12", "", { "dependencies": { "@scalar/openapi-types": "0.1.1", "@unhead/schema": "^1.9.5" } }, "sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ=="],
"@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="],
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
"@tabler/icons": ["@tabler/icons@3.35.0", "", {}, "sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ=="],
"@tabler/icons-react": ["@tabler/icons-react@3.35.0", "", { "dependencies": { "@tabler/icons": "3.35.0" }, "peerDependencies": { "react": ">= 16" } }, "sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g=="],
"@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
"@types/jwt-decode": ["@types/jwt-decode@3.1.0", "", { "dependencies": { "jwt-decode": "*" } }, "sha512-tthwik7TKkou3mVnBnvVuHnHElbjtdbM63pdBCbZTirCt3WAdM73Y79mOri7+ljsS99ZVwUFZHLMxJuJnv/z1w=="],
"@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="],
"@types/node": ["@types/node@24.7.0", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw=="],
"@types/qrcode-terminal": ["@types/qrcode-terminal@0.12.2", "", {}, "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
"@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="],
"add": ["add@2.0.6", "", {}, "sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q=="],
"agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
"archiver": ["archiver@5.3.2", "", { "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" } }, "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw=="],
"archiver-utils": ["archiver-utils@2.1.0", "", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="],
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
"binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="],
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="],
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"buffer-indexof-polyfill": ["buffer-indexof-polyfill@1.0.2", "", {}, "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A=="],
"buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="],
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
"c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="],
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
"chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"compress-commons": ["compress-commons@4.1.2", "", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
"crc32-stream": ["crc32-stream@4.0.3", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw=="],
"cross-fetch": ["cross-fetch@3.1.5", "", { "dependencies": { "node-fetch": "2.6.7" } }, "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="],
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
"devtools-protocol": ["devtools-protocol@0.0.1045489", "", {}, "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ=="],
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
"dompurify": ["dompurify@3.1.7", "", {}, "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ=="],
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
"duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="],
"effect": ["effect@3.16.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg=="],
"elysia": ["elysia@1.4.11", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-cphuzQj0fRw1ICRvwHy2H3xQio9bycaZUVHnDHJQnKqBfMNlZ+Hzj6TMmt9lc0Az0mvbCnPXWVF7y1MCRhUuOA=="],
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="],
"exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="],
"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-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
"file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="],
"fluent-ffmpeg": ["fluent-ffmpeg@2.1.3", "", { "dependencies": { "async": "^0.2.9", "which": "^1.1.1" } }, "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q=="],
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
"fstream": ["fstream@1.0.12", "", { "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" } }, "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
"https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"jose": ["jose@6.1.0", "", {}, "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
"jwt-decode": ["jwt-decode@4.0.0", "", {}, "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="],
"lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
"listenercount": ["listenercount@1.0.1", "", {}, "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
"lodash.difference": ["lodash.difference@4.5.0", "", {}, "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="],
"lodash.flatten": ["lodash.flatten@4.4.0", "", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="],
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
"lodash.union": ["lodash.union@4.6.0", "", {}, "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="],
"mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
"minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
"monaco-editor": ["monaco-editor@0.54.0", "", { "dependencies": { "dompurify": "3.1.7", "marked": "14.0.0" } }, "sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
"node-webpmux": ["node-webpmux@3.1.7", "", {}, "sha512-ySkL4lBCto86OyQ0blAGzylWSECcn5I0lM3bYEhe75T8Zxt/BFUMHa8ktUguR7zwXNdS/Hms31VfSsYKN1383g=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"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=="],
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="],
"postcss-mixins": ["postcss-mixins@12.1.2", "", { "dependencies": { "postcss-js": "^4.0.1", "postcss-simple-vars": "^7.0.1", "sugarss": "^5.0.0", "tinyglobby": "^0.2.14" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-90pSxmZVfbX9e5xCv7tI5RV1mnjdf16y89CJKbf/hD7GyOz1FCxcYMl8ZYA8Hc56dbApTKKmU9HfvgfWdCxlwg=="],
"postcss-nested": ["postcss-nested@7.0.2", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw=="],
"postcss-preset-mantine": ["postcss-preset-mantine@1.18.0", "", { "dependencies": { "postcss-mixins": "^12.0.0", "postcss-nested": "^7.0.2" }, "peerDependencies": { "postcss": ">=8.0.0" } }, "sha512-sP6/s1oC7cOtBdl4mw/IRKmKvYTuzpRrH/vT6v9enMU/EQEQ31eQnHcWtFghOXLH87AAthjL/Q75rLmin1oZoA=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-simple-vars": ["postcss-simple-vars@7.0.1", "", { "peerDependencies": { "postcss": "^8.2.1" } }, "sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A=="],
"prisma": ["prisma@6.17.1", "", { "dependencies": { "@prisma/config": "6.17.1", "@prisma/engines": "6.17.1" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g=="],
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
"puppeteer": ["puppeteer@18.2.1", "", { "dependencies": { "https-proxy-agent": "5.0.1", "progress": "2.0.3", "proxy-from-env": "1.1.0", "puppeteer-core": "18.2.1" } }, "sha512-7+UhmYa7wxPh2oMRwA++k8UGVDxh3YdWFB52r9C3tM81T6BU7cuusUSxImz0GEYSOYUKk/YzIhkQ6+vc0gHbxQ=="],
"puppeteer-core": ["puppeteer-core@18.2.1", "", { "dependencies": { "cross-fetch": "3.1.5", "debug": "4.3.4", "devtools-protocol": "0.0.1045489", "extract-zip": "2.0.1", "https-proxy-agent": "5.0.1", "proxy-from-env": "1.1.0", "rimraf": "3.0.2", "tar-fs": "2.1.1", "unbzip2-stream": "1.4.3", "ws": "8.9.0" } }, "sha512-MRtTAZfQTluz3U2oU/X2VqVWPcR1+94nbA2V6ZrSZRVEwLqZ8eclZ551qGFQD/vD2PYqHJwWOW/fpC721uznVw=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
"qr.js": ["qr.js@0.0.0", "", {}, "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="],
"qrcode-terminal": ["qrcode-terminal@0.12.0", "", { "bin": { "qrcode-terminal": "./bin/qrcode-terminal.js" } }, "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="],
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"react-number-format": ["react-number-format@5.4.4", "", { "peerDependencies": { "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA=="],
"react-qr-code": ["react-qr-code@2.0.18", "", { "dependencies": { "prop-types": "^15.8.1", "qr.js": "0.0.0" }, "peerDependencies": { "react": "*" } }, "sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg=="],
"react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="],
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
"react-router": ["react-router@7.9.4", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA=="],
"react-router-dom": ["react-router-dom@7.9.4", "", { "dependencies": { "react-router": "7.9.4" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA=="],
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"react-textarea-autosize": ["react-textarea-autosize@8.5.9", "", { "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A=="],
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="],
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
"sugarss": ["sugarss@5.0.1", "", { "peerDependencies": { "postcss": "^8.3.3" } }, "sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw=="],
"swr": ["swr@2.3.6", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw=="],
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
"tar-fs": ["tar-fs@2.1.1", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng=="],
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
"unbzip2-stream": ["unbzip2-stream@1.4.3", "", { "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg=="],
"undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"unzipper": ["unzipper@0.10.14", "", { "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", "bluebird": "~3.4.1", "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", "graceful-fs": "^4.2.2", "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" } }, "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g=="],
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
"use-composed-ref": ["use-composed-ref@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w=="],
"use-isomorphic-layout-effect": ["use-isomorphic-layout-effect@1.2.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA=="],
"use-latest": ["use-latest@1.3.0", "", { "dependencies": { "use-isomorphic-layout-effect": "^1.1.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ=="],
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"whatsapp-web.js": ["whatsapp-web.js@1.34.1", "", { "dependencies": { "@pedroslopez/moduleraid": "^5.0.2", "fluent-ffmpeg": "2.1.3", "mime": "^3.0.0", "node-fetch": "^2.6.9", "node-webpmux": "3.1.7", "puppeteer": "^18.2.1" }, "optionalDependencies": { "archiver": "^5.3.1", "fs-extra": "^10.1.0", "unzipper": "^0.10.11" } }, "sha512-IInGEg+F8wB9M+c61KXGZ4bpwq24ew9EgsyMYxUZ/4CQ8GW/afsGseQO4FZriQ16qpu14pFovsD6YrEYxRbyLw=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.9.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
"zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@scalar/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="],
"archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"c12/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"cross-fetch/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
"duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"fluent-ffmpeg/async": ["async@0.2.10", "", {}, "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="],
"giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"nypm/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"puppeteer-core/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="],
"puppeteer-core/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"unzipper/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="],
"@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/themes/@scalar/types/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
"archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"puppeteer-core/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
"unzipper/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
}
}

2
bunfig.toml Normal file
View File

@@ -0,0 +1,2 @@
[serve.static]
env = "BUN_PUBLIC_*"

View File

@@ -0,0 +1,16 @@
{
"from": "6289505046093@c.us",
"fromMe": false,
"body": "halo gaes",
"hasMedia": false,
"type": "chat",
"to": "6289697338821@c.us",
"deviceType": "android",
"media": {
"data": null,
"mimetype": null,
"filename": null,
"filesize": null
},
"notifyName": "jenna ai"
}

187
downloads/fullChat.json Normal file
View File

@@ -0,0 +1,187 @@
{
"_data": {
"id": {
"fromMe": false,
"remote": "6289505046093@c.us",
"id": "AC240D892DE952C48F68249C8D3CAF65",
"_serialized": "false_6289505046093@c.us_AC240D892DE952C48F68249C8D3CAF65"
},
"viewed": false,
"body": "halo gaes",
"type": "chat",
"t": 1760499333,
"clientReceivedTsMillis": 1760499331816,
"notifyName": "jenna ai",
"from": "6289505046093@c.us",
"to": "6289697338821@c.us",
"ack": 1,
"invis": false,
"isNewMsg": true,
"star": false,
"kicNotified": false,
"recvFresh": true,
"isFromTemplate": false,
"thumbnail": "",
"richPreviewType": 0,
"faviconMMSMetadata": null,
"pollInvalidated": false,
"isSentCagPollCreation": false,
"latestEditMsgKey": null,
"latestEditSenderTimestampMs": null,
"mentionedJidList": [],
"groupMentions": [],
"isEventCanceled": false,
"eventInvalidated": false,
"isVcardOverMmsDocument": false,
"questionReplyQuotedMessage": null,
"questionResponsesCount": 0,
"readQuestionResponsesCount": 0,
"hasReaction": false,
"ephemeralDuration": 86400,
"ephemeralSettingTimestamp": 1760421708,
"disappearingModeInitiator": "chat",
"disappearingModeTrigger": "chat_settings",
"disappearingModeInitiatedByMe": false,
"viewMode": "VISIBLE",
"messageSecret": {
"0": 120,
"1": 16,
"2": 223,
"3": 31,
"4": 255,
"5": 95,
"6": 54,
"7": 23,
"8": 5,
"9": 170,
"10": 126,
"11": 183,
"12": 142,
"13": 88,
"14": 30,
"15": 251,
"16": 81,
"17": 183,
"18": 16,
"19": 69,
"20": 158,
"21": 252,
"22": 148,
"23": 155,
"24": 40,
"25": 5,
"26": 20,
"27": 210,
"28": 140,
"29": 42,
"30": 6,
"31": 123
},
"inviteGrpType": "DEFAULT",
"productHeaderImageRejected": false,
"lastPlaybackProgress": 0,
"isDynamicReplyButtonsMsg": false,
"isCarouselCard": false,
"parentMsgId": null,
"callSilenceReason": null,
"isVideoCall": false,
"callDuration": null,
"callCreator": null,
"callParticipants": null,
"isCallLink": null,
"callLinkToken": null,
"isMdHistoryMsg": false,
"stickerSentTs": 0,
"isAvatar": false,
"lastUpdateFromServerTs": 0,
"invokedBotWid": null,
"bizBotType": null,
"botResponseTargetId": null,
"botPluginType": null,
"botPluginReferenceIndex": null,
"botPluginSearchProvider": null,
"botPluginSearchUrl": null,
"botPluginSearchQuery": null,
"botPluginMaybeParent": false,
"botReelPluginThumbnailCdnUrl": null,
"botMessageDisclaimerText": null,
"botMsgBodyType": null,
"reportingTokenInfo": {
"reportingToken": {
"0": 75,
"1": 111,
"2": 0,
"3": 170,
"4": 128,
"5": 198,
"6": 97,
"7": 213,
"8": 204,
"9": 150,
"10": 169,
"11": 92,
"12": 233,
"13": 114,
"14": 228,
"15": 203
},
"version": 2,
"reportingTag": {
"0": 1,
"1": 11,
"2": 67,
"3": 48,
"4": 159,
"5": 94,
"6": 8,
"7": 153,
"8": 152,
"9": 19,
"10": 22,
"11": 148,
"12": 143,
"13": 89,
"14": 9,
"15": 64,
"16": 248,
"17": 149,
"18": 74,
"19": 64
}
},
"requiresDirectConnection": null,
"bizContentPlaceholderType": null,
"hostedBizEncStateMismatch": false,
"senderOrRecipientAccountTypeHosted": false,
"placeholderCreatedWhenAccountIsHosted": false,
"galaxyFlowDisabled": false,
"groupHistoryBundleMessageKey": null,
"groupHistoryBundleMetadata": null,
"links": []
},
"id": {
"fromMe": false,
"remote": "6289505046093@c.us",
"id": "AC240D892DE952C48F68249C8D3CAF65",
"_serialized": "false_6289505046093@c.us_AC240D892DE952C48F68249C8D3CAF65"
},
"ack": 1,
"hasMedia": false,
"body": "halo gaes",
"type": "chat",
"timestamp": 1760499333,
"from": "6289505046093@c.us",
"to": "6289697338821@c.us",
"deviceType": "android",
"forwardingScore": 0,
"isStatus": false,
"isStarred": false,
"fromMe": false,
"hasQuotedMsg": false,
"hasReaction": false,
"vCards": [],
"mentionedIds": [],
"groupMentions": [],
"isGif": false,
"links": []
}

205
downloads/media.json Normal file
View File

@@ -0,0 +1,205 @@
{
"_data": {
"id": {
"fromMe": false,
"remote": "6289505046093@c.us",
"id": "AC1FCF4C63F9B86F93313AAEC7EC5AE5",
"_serialized": "false_6289505046093@c.us_AC1FCF4C63F9B86F93313AAEC7EC5AE5"
},
"viewed": false,
"body": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEABsbGxscGx4hIR4qLSgtKj04MzM4PV1CR0JHQl2NWGdYWGdYjX2Xe3N7l33gsJycsOD/2c7Z//////////////8BGxsbGxwbHiEhHiotKC0qPTgzMzg9XUJHQkdCXY1YZ1hYZ1iNfZd7c3uXfeCwnJyw4P/Zztn////////////////CABEIADwAIQMBIgACEQEDEQH/xAAuAAADAQEBAQAAAAAAAAAAAAAAAwQCBQEGAQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAOtUim5zh/qzFIcWnm3xczjuOoSC/LYkLHsl0WEYOJdj8JoXJUH/xAAlEAACAQIFBAMBAAAAAAAAAAABAgADEQQSEyFREBQxQSIyUmH/2gAIAQEAAT8Aoow+LGaZ5mn/AGGgD5M7ZeeibqJbqMaL2KxMaoNrbRcRSbw0V1bwb9HRTUFvZmgqreZ6ak7i0SsFU5CJ3MoYrTDFtz6jYmqxPzIBmc8xXb0016v7nqXgm83jUXdjZReDCVPewgw1Q+CI1Kqm5E1W4mZuTNR7fYzO36MpVX8Xm3An/8QAFxEBAQEBAAAAAAAAAAAAAAAAEQABMP/aAAgBAgEBPwB2Z3iX/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARMP/aAAgBAwEBPwACyw463//Z",
"type": "image",
"t": 1760499162,
"clientReceivedTsMillis": 1760499161349,
"notifyName": "jenna ai",
"from": "6289505046093@c.us",
"to": "6289697338821@c.us",
"ack": 1,
"invis": false,
"isNewMsg": true,
"star": false,
"kicNotified": false,
"recvFresh": true,
"interactiveAnnotations": [],
"deprecatedMms3Url": "https://mmg.whatsapp.net/v/t62.7118-24/565618455_32971919975740209_8438009621194429801_n.enc?ccb=11-4&oh=01_Q5Aa2wEh_OAabe-vO_8PiWdXDveV3I7vgykLw6MjCihpQdmq7w&oe=69169DCC&_nc_sid=5e03e0&mms3=true",
"directPath": "/v/t62.7118-24/565618455_32971919975740209_8438009621194429801_n.enc?ccb=11-4&oh=01_Q5Aa2wEh_OAabe-vO_8PiWdXDveV3I7vgykLw6MjCihpQdmq7w&oe=69169DCC&_nc_sid=5e03e0",
"mimetype": "image/jpeg",
"filehash": "IveoyrubZqm4f8VrvH5lU9zN/M6kXddXM6GchNIoBf8=",
"encFilehash": "LP/NA3twuiOjLyl7bnq6Tavsu7MT0hKa2v0/ts0wkzI=",
"size": 47276,
"mediaKey": "7eEyqrxGDsJXoGIBxbmeo2PdbO0T+I8KcSqZWJmfvLo=",
"mediaKeyTimestamp": 1760498519,
"isViewOnce": false,
"width": 720,
"height": 1280,
"staticUrl": "",
"scanLengths": [
7339,
17711,
7801,
14425
],
"scansSidecar": {},
"isFromTemplate": false,
"pollInvalidated": false,
"isSentCagPollCreation": false,
"latestEditMsgKey": null,
"latestEditSenderTimestampMs": null,
"mentionedJidList": [],
"groupMentions": [],
"isEventCanceled": false,
"eventInvalidated": false,
"statusMentioned": false,
"isVcardOverMmsDocument": false,
"questionReplyQuotedMessage": null,
"questionResponsesCount": 0,
"readQuestionResponsesCount": 0,
"hasReaction": false,
"ephemeralDuration": 86400,
"ephemeralSettingTimestamp": 1760421708,
"disappearingModeInitiator": "chat",
"disappearingModeTrigger": "chat_settings",
"disappearingModeInitiatedByMe": false,
"viewMode": "VISIBLE",
"messageSecret": {
"0": 29,
"1": 141,
"2": 90,
"3": 218,
"4": 12,
"5": 186,
"6": 69,
"7": 134,
"8": 140,
"9": 175,
"10": 233,
"11": 234,
"12": 147,
"13": 5,
"14": 112,
"15": 20,
"16": 135,
"17": 4,
"18": 190,
"19": 145,
"20": 68,
"21": 243,
"22": 86,
"23": 109,
"24": 9,
"25": 235,
"26": 250,
"27": 31,
"28": 255,
"29": 149,
"30": 147,
"31": 124
},
"productHeaderImageRejected": false,
"lastPlaybackProgress": 0,
"isDynamicReplyButtonsMsg": false,
"isCarouselCard": false,
"parentMsgId": null,
"callSilenceReason": null,
"isVideoCall": false,
"callDuration": null,
"callCreator": null,
"callParticipants": null,
"isCallLink": null,
"callLinkToken": null,
"isMdHistoryMsg": false,
"stickerSentTs": 0,
"isAvatar": false,
"lastUpdateFromServerTs": 0,
"invokedBotWid": null,
"bizBotType": null,
"botResponseTargetId": null,
"botPluginType": null,
"botPluginReferenceIndex": null,
"botPluginSearchProvider": null,
"botPluginSearchUrl": null,
"botPluginSearchQuery": null,
"botPluginMaybeParent": false,
"botReelPluginThumbnailCdnUrl": null,
"botMessageDisclaimerText": null,
"botMsgBodyType": null,
"reportingTokenInfo": {
"reportingToken": {
"0": 22,
"1": 36,
"2": 226,
"3": 57,
"4": 30,
"5": 125,
"6": 123,
"7": 89,
"8": 148,
"9": 96,
"10": 59,
"11": 132,
"12": 196,
"13": 230,
"14": 192,
"15": 44
},
"version": 2,
"reportingTag": {
"0": 1,
"1": 11,
"2": 216,
"3": 114,
"4": 105,
"5": 176,
"6": 57,
"7": 186,
"8": 62,
"9": 102,
"10": 166,
"11": 62,
"12": 206,
"13": 161,
"14": 215,
"15": 245,
"16": 83,
"17": 227,
"18": 232,
"19": 76
}
},
"requiresDirectConnection": null,
"bizContentPlaceholderType": null,
"hostedBizEncStateMismatch": false,
"senderOrRecipientAccountTypeHosted": false,
"placeholderCreatedWhenAccountIsHosted": false,
"galaxyFlowDisabled": false,
"groupHistoryBundleMessageKey": null,
"groupHistoryBundleMetadata": null,
"links": []
},
"mediaKey": "7eEyqrxGDsJXoGIBxbmeo2PdbO0T+I8KcSqZWJmfvLo=",
"id": {
"fromMe": false,
"remote": "6289505046093@c.us",
"id": "AC1FCF4C63F9B86F93313AAEC7EC5AE5",
"_serialized": "false_6289505046093@c.us_AC1FCF4C63F9B86F93313AAEC7EC5AE5"
},
"ack": 1,
"hasMedia": true,
"body": "",
"type": "image",
"timestamp": 1760499162,
"from": "6289505046093@c.us",
"to": "6289697338821@c.us",
"deviceType": "android",
"forwardingScore": 0,
"isStatus": false,
"isStarred": false,
"fromMe": false,
"hasQuotedMsg": false,
"hasReaction": false,
"vCards": [],
"mentionedIds": [],
"groupMentions": [],
"isGif": false,
"links": []
}

1
generated/prisma/client.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./index"

View File

@@ -0,0 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
module.exports = { ...require('.') }

1
generated/prisma/default.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./index"

View File

@@ -0,0 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
module.exports = { ...require('#main-entry-point') }

1
generated/prisma/edge.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./default"

224
generated/prisma/edge.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,211 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
objectEnumValues,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('./runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.17.1
* Query Engine version: 272a37d34178c2894197e17273bf937f25acdeac
*/
Prisma.prismaVersion = {
client: "6.17.1",
engine: "272a37d34178c2894197e17273bf937f25acdeac"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
name: 'name',
email: 'email',
password: 'password',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.ApiKeyScalarFieldEnum = {
id: 'id',
userId: 'userId',
name: 'name',
key: 'key',
description: 'description',
expiredAt: 'expiredAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.WebHookScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
url: 'url',
payload: 'payload',
method: 'method',
headers: 'headers',
apiToken: 'apiToken',
retries: 'retries',
enabled: 'enabled',
replay: 'replay',
replayKey: 'replayKey',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.ModelName = {
User: 'User',
ApiKey: 'ApiKey',
WebHook: 'WebHook'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)

5787
generated/prisma/index.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

245
generated/prisma/index.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,183 @@
{
"name": "prisma-client-2e3b759cf6e55c6417c5fd25161bbd379e191c301fa0ac5f45716fca6873db7a",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",
"exports": {
"./client": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./package.json": "./package.json",
".": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./react-native": {
"types": "./react-native.d.ts",
"require": "./react-native.js",
"import": "./react-native.js",
"default": "./react-native.js"
},
"./extension": {
"types": "./extension.d.ts",
"require": "./extension.js",
"import": "./extension.js",
"default": "./extension.js"
},
"./index-browser": {
"types": "./index.d.ts",
"require": "./index-browser.js",
"import": "./index-browser.js",
"default": "./index-browser.js"
},
"./index": {
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js",
"default": "./index.js"
},
"./wasm": {
"types": "./wasm.d.ts",
"require": "./wasm.js",
"import": "./wasm.mjs",
"default": "./wasm.mjs"
},
"./runtime/client": {
"types": "./runtime/client.d.ts",
"node": {
"require": "./runtime/client.js",
"default": "./runtime/client.js"
},
"require": "./runtime/client.js",
"import": "./runtime/client.mjs",
"default": "./runtime/client.mjs"
},
"./runtime/library": {
"types": "./runtime/library.d.ts",
"require": "./runtime/library.js",
"import": "./runtime/library.mjs",
"default": "./runtime/library.mjs"
},
"./runtime/binary": {
"types": "./runtime/binary.d.ts",
"require": "./runtime/binary.js",
"import": "./runtime/binary.mjs",
"default": "./runtime/binary.mjs"
},
"./runtime/wasm-engine-edge": {
"types": "./runtime/wasm-engine-edge.d.ts",
"require": "./runtime/wasm-engine-edge.js",
"import": "./runtime/wasm-engine-edge.mjs",
"default": "./runtime/wasm-engine-edge.mjs"
},
"./runtime/wasm-compiler-edge": {
"types": "./runtime/wasm-compiler-edge.d.ts",
"require": "./runtime/wasm-compiler-edge.js",
"import": "./runtime/wasm-compiler-edge.mjs",
"default": "./runtime/wasm-compiler-edge.mjs"
},
"./runtime/edge": {
"types": "./runtime/edge.d.ts",
"require": "./runtime/edge.js",
"import": "./runtime/edge-esm.js",
"default": "./runtime/edge-esm.js"
},
"./runtime/react-native": {
"types": "./runtime/react-native.d.ts",
"require": "./runtime/react-native.js",
"import": "./runtime/react-native.js",
"default": "./runtime/react-native.js"
},
"./runtime/index-browser": {
"types": "./runtime/index-browser.d.ts",
"require": "./runtime/index-browser.js",
"import": "./runtime/index-browser.mjs",
"default": "./runtime/index-browser.mjs"
},
"./generator-build": {
"require": "./generator-build/index.js",
"import": "./generator-build/index.js",
"default": "./generator-build/index.js"
},
"./sql": {
"require": {
"types": "./sql.d.ts",
"node": "./sql.js",
"default": "./sql.js"
},
"import": {
"types": "./sql.d.ts",
"node": "./sql.mjs",
"default": "./sql.mjs"
},
"default": "./sql.js"
},
"./*": "./*"
},
"version": "6.17.1",
"sideEffects": false,
"imports": {
"#wasm-engine-loader": {
"edge-light": "./wasm-edge-light-loader.mjs",
"workerd": "./wasm-worker-loader.mjs",
"worker": "./wasm-worker-loader.mjs",
"default": "./wasm-worker-loader.mjs"
},
"#main-entry-point": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
}
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,370 @@
declare class AnyNull extends NullTypesEnumValue {
#private;
}
declare type Args<T, F extends Operation> = T extends {
[K: symbol]: {
types: {
operations: {
[K in F]: {
args: any;
};
};
};
};
} ? T[symbol]['types']['operations'][F]['args'] : any;
declare class DbNull extends NullTypesEnumValue {
#private;
}
export declare function Decimal(n: Decimal.Value): Decimal;
export declare namespace Decimal {
export type Constructor = typeof Decimal;
export type Instance = Decimal;
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export type Modulo = Rounding | 9;
export type Value = string | number | Decimal;
// http://mikemcl.github.io/decimal.js/#constructor-properties
export interface Config {
precision?: number;
rounding?: Rounding;
toExpNeg?: number;
toExpPos?: number;
minE?: number;
maxE?: number;
crypto?: boolean;
modulo?: Modulo;
defaults?: boolean;
}
}
export declare class Decimal {
readonly d: number[];
readonly e: number;
readonly s: number;
constructor(n: Decimal.Value);
absoluteValue(): Decimal;
abs(): Decimal;
ceil(): Decimal;
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
comparedTo(n: Decimal.Value): number;
cmp(n: Decimal.Value): number;
cosine(): Decimal;
cos(): Decimal;
cubeRoot(): Decimal;
cbrt(): Decimal;
decimalPlaces(): number;
dp(): number;
dividedBy(n: Decimal.Value): Decimal;
div(n: Decimal.Value): Decimal;
dividedToIntegerBy(n: Decimal.Value): Decimal;
divToInt(n: Decimal.Value): Decimal;
equals(n: Decimal.Value): boolean;
eq(n: Decimal.Value): boolean;
floor(): Decimal;
greaterThan(n: Decimal.Value): boolean;
gt(n: Decimal.Value): boolean;
greaterThanOrEqualTo(n: Decimal.Value): boolean;
gte(n: Decimal.Value): boolean;
hyperbolicCosine(): Decimal;
cosh(): Decimal;
hyperbolicSine(): Decimal;
sinh(): Decimal;
hyperbolicTangent(): Decimal;
tanh(): Decimal;
inverseCosine(): Decimal;
acos(): Decimal;
inverseHyperbolicCosine(): Decimal;
acosh(): Decimal;
inverseHyperbolicSine(): Decimal;
asinh(): Decimal;
inverseHyperbolicTangent(): Decimal;
atanh(): Decimal;
inverseSine(): Decimal;
asin(): Decimal;
inverseTangent(): Decimal;
atan(): Decimal;
isFinite(): boolean;
isInteger(): boolean;
isInt(): boolean;
isNaN(): boolean;
isNegative(): boolean;
isNeg(): boolean;
isPositive(): boolean;
isPos(): boolean;
isZero(): boolean;
lessThan(n: Decimal.Value): boolean;
lt(n: Decimal.Value): boolean;
lessThanOrEqualTo(n: Decimal.Value): boolean;
lte(n: Decimal.Value): boolean;
logarithm(n?: Decimal.Value): Decimal;
log(n?: Decimal.Value): Decimal;
minus(n: Decimal.Value): Decimal;
sub(n: Decimal.Value): Decimal;
modulo(n: Decimal.Value): Decimal;
mod(n: Decimal.Value): Decimal;
naturalExponential(): Decimal;
exp(): Decimal;
naturalLogarithm(): Decimal;
ln(): Decimal;
negated(): Decimal;
neg(): Decimal;
plus(n: Decimal.Value): Decimal;
add(n: Decimal.Value): Decimal;
precision(includeZeros?: boolean): number;
sd(includeZeros?: boolean): number;
round(): Decimal;
sine() : Decimal;
sin() : Decimal;
squareRoot(): Decimal;
sqrt(): Decimal;
tangent() : Decimal;
tan() : Decimal;
times(n: Decimal.Value): Decimal;
mul(n: Decimal.Value) : Decimal;
toBinary(significantDigits?: number): string;
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
toDecimalPlaces(decimalPlaces?: number): Decimal;
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toDP(decimalPlaces?: number): Decimal;
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toExponential(decimalPlaces?: number): string;
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFixed(decimalPlaces?: number): string;
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFraction(max_denominator?: Decimal.Value): Decimal[];
toHexadecimal(significantDigits?: number): string;
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
toHex(significantDigits?: number): string;
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
toJSON(): string;
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
toNumber(): number;
toOctal(significantDigits?: number): string;
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
toPower(n: Decimal.Value): Decimal;
pow(n: Decimal.Value): Decimal;
toPrecision(significantDigits?: number): string;
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
toSignificantDigits(significantDigits?: number): Decimal;
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toSD(significantDigits?: number): Decimal;
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toString(): string;
truncated(): Decimal;
trunc(): Decimal;
valueOf(): string;
static abs(n: Decimal.Value): Decimal;
static acos(n: Decimal.Value): Decimal;
static acosh(n: Decimal.Value): Decimal;
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
static asin(n: Decimal.Value): Decimal;
static asinh(n: Decimal.Value): Decimal;
static atan(n: Decimal.Value): Decimal;
static atanh(n: Decimal.Value): Decimal;
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
static cbrt(n: Decimal.Value): Decimal;
static ceil(n: Decimal.Value): Decimal;
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
static clone(object?: Decimal.Config): Decimal.Constructor;
static config(object: Decimal.Config): Decimal.Constructor;
static cos(n: Decimal.Value): Decimal;
static cosh(n: Decimal.Value): Decimal;
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
static exp(n: Decimal.Value): Decimal;
static floor(n: Decimal.Value): Decimal;
static hypot(...n: Decimal.Value[]): Decimal;
static isDecimal(object: any): object is Decimal;
static ln(n: Decimal.Value): Decimal;
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
static log2(n: Decimal.Value): Decimal;
static log10(n: Decimal.Value): Decimal;
static max(...n: Decimal.Value[]): Decimal;
static min(...n: Decimal.Value[]): Decimal;
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
static noConflict(): Decimal.Constructor; // Browser only
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
static random(significantDigits?: number): Decimal;
static round(n: Decimal.Value): Decimal;
static set(object: Decimal.Config): Decimal.Constructor;
static sign(n: Decimal.Value): number;
static sin(n: Decimal.Value): Decimal;
static sinh(n: Decimal.Value): Decimal;
static sqrt(n: Decimal.Value): Decimal;
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
static sum(...n: Decimal.Value[]): Decimal;
static tan(n: Decimal.Value): Decimal;
static tanh(n: Decimal.Value): Decimal;
static trunc(n: Decimal.Value): Decimal;
static readonly default?: Decimal.Constructor;
static readonly Decimal?: Decimal.Constructor;
static readonly precision: number;
static readonly rounding: Decimal.Rounding;
static readonly toExpNeg: number;
static readonly toExpPos: number;
static readonly minE: number;
static readonly maxE: number;
static readonly crypto: boolean;
static readonly modulo: Decimal.Modulo;
static readonly ROUND_UP: 0;
static readonly ROUND_DOWN: 1;
static readonly ROUND_CEIL: 2;
static readonly ROUND_FLOOR: 3;
static readonly ROUND_HALF_UP: 4;
static readonly ROUND_HALF_DOWN: 5;
static readonly ROUND_HALF_EVEN: 6;
static readonly ROUND_HALF_CEIL: 7;
static readonly ROUND_HALF_FLOOR: 8;
static readonly EUCLID: 9;
}
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
[K in keyof A]: Exact<A[K], W[K]>;
} : W) : never) | (A extends Narrowable ? A : never);
export declare function getRuntime(): GetRuntimeOutput;
declare type GetRuntimeOutput = {
id: RuntimeName;
prettyName: string;
isEdge: boolean;
};
declare class JsonNull extends NullTypesEnumValue {
#private;
}
/**
* Generates more strict variant of an enum which, unlike regular enum,
* throws on non-existing property access. This can be useful in following situations:
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
* - enum values are generated dynamically from DMMF.
*
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
* will result in `undefined` value being used, which will be accepted. Using strict enum
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
*
* Note: if you need to check for existence of a value in the enum you can still use either
* `in` operator or `hasOwnProperty` function.
*
* @param definition
* @returns
*/
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
declare type Narrowable = string | number | bigint | boolean | [];
declare class NullTypesEnumValue extends ObjectEnumValue {
_getNamespace(): string;
}
/**
* Base class for unique values of object-valued enums.
*/
declare abstract class ObjectEnumValue {
constructor(arg?: symbol);
abstract _getNamespace(): string;
_getName(): string;
toString(): string;
}
export declare const objectEnumValues: {
classes: {
DbNull: typeof DbNull;
JsonNull: typeof JsonNull;
AnyNull: typeof AnyNull;
};
instances: {
DbNull: DbNull;
JsonNull: JsonNull;
AnyNull: AnyNull;
};
};
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
declare namespace Public {
export {
validator
}
}
export { Public }
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
export { }

File diff suppressed because one or more lines are too long

3977
generated/prisma/runtime/library.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,48 @@
generator client {
provider = "prisma-client-js"
output = "../generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ApiKey ApiKey[]
}
model ApiKey {
id String @id @default(cuid())
User User? @relation(fields: [userId], references: [id])
userId String
name String
key String @unique @db.Text
description String?
expiredAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model WebHook {
id String @id @default(cuid())
name String?
description String?
url String
payload String? @default("{}")
method String @default("POST")
headers String? @default("{}")
apiToken String?
retries Int? @default(3)
enabled Boolean @default(true)
replay Boolean @default(false)
replayKey String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@@ -0,0 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
export default import('./query_engine_bg.wasm?module')

View File

@@ -0,0 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
export default import('./query_engine_bg.wasm')

1
generated/prisma/wasm.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./default"

231
generated/prisma/wasm.js Normal file
View File

@@ -0,0 +1,231 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
Object.defineProperty(exports, "__esModule", { value: true });
const {
PrismaClientKnownRequestError,
PrismaClientUnknownRequestError,
PrismaClientRustPanicError,
PrismaClientInitializationError,
PrismaClientValidationError,
getPrismaClient,
sqltag,
empty,
join,
raw,
skip,
Decimal,
Debug,
objectEnumValues,
makeStrictEnum,
Extensions,
warnOnce,
defineDmmfProperty,
Public,
getRuntime,
createParam,
} = require('./runtime/wasm-engine-edge.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.17.1
* Query Engine version: 272a37d34178c2894197e17273bf937f25acdeac
*/
Prisma.prismaVersion = {
client: "6.17.1",
engine: "272a37d34178c2894197e17273bf937f25acdeac"
}
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
Prisma.PrismaClientValidationError = PrismaClientValidationError
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = sqltag
Prisma.empty = empty
Prisma.join = join
Prisma.raw = raw
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = Extensions.getExtensionContext
Prisma.defineExtension = Extensions.defineExtension
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
name: 'name',
email: 'email',
password: 'password',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.ApiKeyScalarFieldEnum = {
id: 'id',
userId: 'userId',
name: 'name',
key: 'key',
description: 'description',
expiredAt: 'expiredAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.WebHookScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
url: 'url',
payload: 'payload',
method: 'method',
headers: 'headers',
apiToken: 'apiToken',
retries: 'retries',
enabled: 'enabled',
replay: 'replay',
replayKey: 'replayKey',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.ModelName = {
User: 'User',
ApiKey: 'ApiKey',
WebHook: 'WebHook'
};
/**
* Create the Client
*/
const config = {
"generator": {
"name": "client",
"provider": {
"fromEnvVar": null,
"value": "prisma-client-js"
},
"output": {
"value": "/Users/bip/Documents/projects/jenna/wajs-server/generated/prisma",
"fromEnvVar": null
},
"config": {
"engineType": "library"
},
"binaryTargets": [
{
"fromEnvVar": null,
"value": "darwin-arm64",
"native": true
}
],
"previewFeatures": [],
"sourceFilePath": "/Users/bip/Documents/projects/jenna/wajs-server/prisma/schema.prisma",
"isCustomOutput": true
},
"relativeEnvPaths": {
"rootEnvPath": null,
"schemaEnvPath": "../../.env"
},
"relativePath": "../../prisma",
"clientVersion": "6.17.1",
"engineVersion": "272a37d34178c2894197e17273bf937f25acdeac",
"datasourceNames": [
"db"
],
"activeProvider": "postgresql",
"inlineDatasources": {
"db": {
"url": {
"fromEnvVar": "DATABASE_URL",
"value": null
}
}
},
"inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel User {\n id String @id @default(cuid())\n name String?\n email String? @unique\n password String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n ApiKey ApiKey[]\n}\n\nmodel ApiKey {\n id String @id @default(cuid())\n User User? @relation(fields: [userId], references: [id])\n userId String\n name String\n key String @unique @db.Text\n description String?\n expiredAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WebHook {\n id String @id @default(cuid())\n name String?\n description String?\n url String\n payload String? @default(\"{}\")\n method String @default(\"POST\")\n headers String? @default(\"{}\")\n apiToken String?\n retries Int? @default(3)\n enabled Boolean @default(true)\n replay Boolean @default(false)\n replayKey String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
"inlineSchemaHash": "b1c74fdf8c8c71cfbe0b140d02ba653d858d37e6ff4828268dfac154524e957b",
"copyEngine": true
}
config.dirname = '/'
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"ApiKey\",\"kind\":\"object\",\"type\":\"ApiKey\",\"relationName\":\"ApiKeyToUser\"}],\"dbName\":null},\"ApiKey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"User\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ApiKeyToUser\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiredAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WebHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"method\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"headers\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"apiToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"retries\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replay\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replayKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
config.engineWasm = {
getRuntime: async () => require('./query_engine_bg.js'),
getQueryEngineWasmModule: async () => {
const loader = (await import('#wasm-engine-loader')).default
const engine = (await loader).default
return engine
}
}
config.compilerWasm = undefined
config.injectableEdgeEnv = () => ({
parsed: {
DATABASE_URL: typeof globalThis !== 'undefined' && globalThis['DATABASE_URL'] || typeof process !== 'undefined' && process.env && process.env.DATABASE_URL || undefined
}
})
if (typeof globalThis !== 'undefined' && globalThis['DEBUG'] || typeof process !== 'undefined' && process.env && process.env.DEBUG || undefined) {
Debug.enable(typeof globalThis !== 'undefined' && globalThis['DEBUG'] || typeof process !== 'undefined' && process.env && process.env.DEBUG || undefined)
}
const PrismaClient = getPrismaClient(config)
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)

1530
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

50
package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "bun-react-template",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "bun --hot src/index.tsx",
"build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'",
"start": "NODE_ENV=production bun src/index.tsx",
"seed": "bun prisma/seed.ts"
},
"dependencies": {
"@elysiajs/cors": "^1.4.0",
"@elysiajs/eden": "^1.4.4",
"@elysiajs/jwt": "^1.4.0",
"@elysiajs/swagger": "^1.3.1",
"@lglab/react-qr-code": "^1.4.5",
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/modals": "^8.3.5",
"@mantine/notifications": "^8.3.4",
"@monaco-editor/react": "^4.7.0",
"@prisma/client": "^6.17.1",
"@tabler/icons-react": "^3.35.0",
"@types/jwt-decode": "^3.1.0",
"@types/lodash": "^4.17.20",
"@types/qrcode-terminal": "^0.12.2",
"add": "^2.0.6",
"elysia": "^1.4.11",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"qrcode-terminal": "^0.12.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-qr-code": "^2.0.18",
"react-router-dom": "^7.9.4",
"swr": "^2.3.6",
"uuid": "^13.0.0",
"whatsapp-web.js": "^1.34.1"
},
"devDependencies": {
"@types/bun": "latest",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"postcss": "^8.5.6",
"postcss-preset-mantine": "^1.18.0",
"postcss-simple-vars": "^7.0.1",
"prisma": "^6.17.1"
}
}

16
postcss.config.js Normal file
View File

@@ -0,0 +1,16 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};

48
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,48 @@
generator client {
provider = "prisma-client-js"
output = "../generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ApiKey ApiKey[]
}
model ApiKey {
id String @id @default(cuid())
User User? @relation(fields: [userId], references: [id])
userId String
name String
key String @unique @db.Text
description String?
expiredAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model WebHook {
id String @id @default(cuid())
name String?
description String?
url String
payload String? @default("{}")
method String @default("POST")
headers String? @default("{}")
apiToken String?
retries Int? @default(3)
enabled Boolean @default(true)
replay Boolean @default(false)
replayKey String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

30
prisma/seed.ts Normal file
View File

@@ -0,0 +1,30 @@
import { prisma } from "@/server/lib/prisma";
const user = [
{
name: "Bip",
email: "bip@bip.com",
password: "bip",
}
];
; (async () => {
for (const u of user) {
await prisma.user.upsert({
where: { email: u.email },
create: u,
update: u,
})
console.log(`✅ User ${u.email} seeded successfully`)
}
})().catch((e) => {
console.error(e)
process.exit(1)
}).finally(() => {
console.log("✅ Seeding completed successfully ")
process.exit(0)
})

17
src/App.tsx Normal file
View File

@@ -0,0 +1,17 @@
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import { Notifications } from "@mantine/notifications";
import { ModalsProvider } from "@mantine/modals";
import { MantineProvider } from "@mantine/core";
import AppRoutes from "./AppRoutes";
export function App() {
return (
<MantineProvider>
<Notifications />
<ModalsProvider>
<AppRoutes />
</ModalsProvider>
</MantineProvider>
);
}

64
src/AppRoutes.tsx Normal file
View File

@@ -0,0 +1,64 @@
// ⚡ Auto-generated by generateRoutes.ts — DO NOT EDIT MANUALLY
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Qrcode from "./pages/wajs/qrcode";
import WebhookCreate from "./pages/sq/dashboard/webhook/webhook_create";
import WebhookEdit from "./pages/sq/dashboard/webhook/webhook_edit";
import WebhookHome from "./pages/sq/dashboard/webhook/webhook_home";
import WebhookLayout from "./pages/sq/dashboard/webhook/webhook_layout";
import WajsHome from "./pages/sq/dashboard/wajs/wajs_home";
import WajsLayout from "./pages/sq/dashboard/wajs/wajs_layout";
import ApikeyPage from "./pages/sq/dashboard/apikey/apikey_page";
import DashboardPage from "./pages/sq/dashboard/dashboard_page";
import DashboardLayout from "./pages/sq/dashboard/dashboard_layout";
import SqLayout from "./pages/sq/sq_layout";
import Login from "./pages/Login";
import Home from "./pages/Home";
import NotFound from "./pages/NotFound";
export default function AppRoutes() {
return (
<BrowserRouter>
<Routes>
<Route path="/wajs/qrcode" element={<Qrcode />} />
<Route path="/sq" element={<SqLayout />}>
<Route path="/sq/dashboard" element={<DashboardLayout />}>
<Route path="/sq/dashboard/webhook" element={<WebhookLayout />}>
<Route index element={<WebhookHome />} />
<Route
path="/sq/dashboard/webhook/webhook-create"
element={<WebhookCreate />}
/>
<Route
path="/sq/dashboard/webhook/webhook-edit"
element={<WebhookEdit />}
/>
<Route
path="/sq/dashboard/webhook/webhook-home"
element={<WebhookHome />}
/>
</Route>
<Route path="/sq/dashboard/wajs" element={<WajsLayout />}>
<Route index element={<WajsHome />} />
<Route
path="/sq/dashboard/wajs/wajs-home"
element={<WajsHome />}
/>
</Route>
<Route
path="/sq/dashboard/apikey/apikey"
element={<ApikeyPage />}
/>
<Route path="/sq/dashboard/dashboard" element={<DashboardPage />} />
</Route>
</Route>
<Route path="/login" element={<Login />} />
<Route path="/" element={<Home />} />
<Route path="/*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}

19
src/clientRoutes.ts Normal file
View File

@@ -0,0 +1,19 @@
// AUTO-GENERATED FILE
const clientRoutes = {
"/wajs/qrcode": "/wajs/qrcode",
"/sq": "/sq",
"/sq/dashboard": "/sq/dashboard",
"/sq/dashboard/webhook": "/sq/dashboard/webhook",
"/sq/dashboard/webhook/webhook-create": "/sq/dashboard/webhook/webhook-create",
"/sq/dashboard/webhook/webhook-edit": "/sq/dashboard/webhook/webhook-edit",
"/sq/dashboard/webhook/webhook-home": "/sq/dashboard/webhook/webhook-home",
"/sq/dashboard/wajs": "/sq/dashboard/wajs",
"/sq/dashboard/wajs/wajs-home": "/sq/dashboard/wajs/wajs-home",
"/sq/dashboard/apikey/apikey": "/sq/dashboard/apikey/apikey",
"/sq/dashboard/dashboard": "/sq/dashboard/dashboard",
"/login": "/login",
"/": "/",
"/*": "/*"
} as const;
export default clientRoutes;

View File

@@ -0,0 +1,25 @@
import { useEffect, useState } from "react";
import { Navigate, Outlet } from "react-router-dom";
import clientRoutes from "@/clientRoutes";
import apiFetch from "@/lib/apiFetch";
export default function ProtectedRoute() {
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
useEffect(() => {
async function checkSession() {
try {
// backend otomatis baca cookie JWT dari request
const res = await apiFetch.api.user.find.get();
setIsAuthenticated(res.status === 200);
} catch {
setIsAuthenticated(false);
}
}
checkSession();
}, []);
if (isAuthenticated === null) return null; // or loading spinner
if (!isAuthenticated) return <Navigate to={clientRoutes["/login"]} replace />;
return <Outlet />;
}

26
src/frontend.tsx Normal file
View File

@@ -0,0 +1,26 @@
/**
* This file is the entry point for the React app, it sets up the root
* element and renders the App component to the DOM.
*
* It is included in `src/index.html`.
*/
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
const elem = document.getElementById("root")!;
const app = (
<StrictMode>
<App />
</StrictMode>
);
if (import.meta.hot) {
// With hot module reloading, `import.meta.hot.data` is persisted.
const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(app);
} else {
// The hot module reloading API is not available in production.
createRoot(elem).render(app);
}

187
src/index.css Normal file
View File

@@ -0,0 +1,187 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
}
body {
margin: 0;
display: grid;
place-items: center;
min-width: 320px;
min-height: 100vh;
position: relative;
}
body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
opacity: 0.05;
background: url("./logo.svg");
background-size: 256px;
transform: rotate(-12deg) scale(1.35);
animation: slide 30s linear infinite;
pointer-events: none;
}
@keyframes slide {
from {
background-position: 0 0;
}
to {
background-position: 256px 224px;
}
}
.app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
position: relative;
z-index: 1;
}
.logo-container {
display: flex;
justify-content: center;
align-items: center;
gap: 2rem;
margin-bottom: 2rem;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 0.3s;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.bun-logo {
transform: scale(1.2);
}
.bun-logo:hover {
filter: drop-shadow(0 0 2em #fbf0dfaa);
}
.react-logo {
animation: spin 20s linear infinite;
}
.react-logo:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes spin {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
code {
background-color: #1a1a1a;
padding: 0.2em 0.4em;
border-radius: 0.3em;
font-family: monospace;
}
.api-tester {
margin: 2rem auto 0;
width: 100%;
max-width: 600px;
text-align: left;
display: flex;
flex-direction: column;
gap: 1rem;
}
.endpoint-row {
display: flex;
align-items: center;
gap: 0.5rem;
background: #1a1a1a;
padding: 0.75rem;
border-radius: 12px;
font: monospace;
border: 2px solid #fbf0df;
transition: 0.3s;
width: 100%;
box-sizing: border-box;
}
.endpoint-row:focus-within {
border-color: #f3d5a3;
}
.method {
background: #fbf0df;
color: #1a1a1a;
padding: 0.3rem 0.7rem;
border-radius: 8px;
font-weight: 700;
font-size: 0.9em;
appearance: none;
margin: 0;
width: min-content;
display: block;
flex-shrink: 0;
border: none;
}
.method option {
text-align: left;
}
.url-input {
width: 100%;
flex: 1;
background: 0;
border: 0;
color: #fbf0df;
font: 1em monospace;
padding: 0.2rem;
outline: 0;
}
.url-input:focus {
color: #fff;
}
.url-input::placeholder {
color: rgba(251, 240, 223, 0.4);
}
.send-button {
background: #fbf0df;
color: #1a1a1a;
border: 0;
padding: 0.4rem 1.2rem;
border-radius: 8px;
font-weight: 700;
transition: 0.1s;
cursor: var(--bun-cursor);
}
.send-button:hover {
background: #f3d5a3;
transform: translateY(-1px);
cursor: pointer;
}
.response-area {
width: 100%;
min-height: 120px;
background: #1a1a1a;
border: 2px solid #fbf0df;
border-radius: 12px;
padding: 0.75rem;
color: #fbf0df;
font: monospace;
resize: vertical;
box-sizing: border-box;
}
.response-area:focus {
border-color: #f3d5a3;
}
.response-area::placeholder {
color: rgba(251, 240, 223, 0.4);
}
@media (prefers-reduced-motion) {
*,
::before,
::after {
animation: none !important;
}
}

13
src/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="./logo.svg" />
<title>Bun + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>

46
src/index.tsx Normal file
View File

@@ -0,0 +1,46 @@
import Elysia, { t } from "elysia";
import Swagger from "@elysiajs/swagger";
import html from "./index.html";
import Dashboard from "./server/routes/darmasaba";
import apiAuth from "./server/middlewares/apiAuth";
import Auth from "./server/routes/auth_route";
import ApiKeyRoute from "./server/routes/apikey_route";
import type { User } from "generated/prisma";
import WaRoute from "./server/routes/wa_route";
import WebhookRoute from "./server/routes/webhook_route";
const Docs = new Elysia().use(
Swagger({
path: "/docs",
}),
);
const ApiUser = new Elysia({
prefix: "/user",
}).get("/find", (ctx) => {
const { user } = ctx as any;
return {
user: user as User,
};
});
const Api = new Elysia({
prefix: "/api",
})
.use(apiAuth)
.use(ApiKeyRoute)
.use(Dashboard)
.use(ApiUser)
.use(WaRoute)
.use(WebhookRoute);
const app = new Elysia()
.use(Api)
.use(Docs)
.use(Auth)
.get("*", html)
.listen(3000, () => {
console.log("Server running at http://localhost:3000");
});
export type ServerApp = typeof app;

11
src/lib/apiFetch.ts Normal file
View File

@@ -0,0 +1,11 @@
import { treaty } from '@elysiajs/eden'
import type { ServerApp } from '..'
const URL = process.env.BUN_PUBLIC_BASE_URL
if (!URL) {
throw new Error('BUN_PUBLIC_BASE_URL is not defined')
}
const apiFetch = treaty<ServerApp>(URL)
export default apiFetch

1
src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg id="Bun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 70"><title>Bun Logo</title><path id="Shadow" d="M71.09,20.74c-.16-.17-.33-.34-.5-.5s-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5A26.46,26.46,0,0,1,75.5,35.7c0,16.57-16.82,30.05-37.5,30.05-11.58,0-21.94-4.23-28.83-10.86l.5.5.5.5.5.5.5.5.5.5.5.5.5.5C19.55,65.3,30.14,69.75,42,69.75c20.68,0,37.5-13.48,37.5-30C79.5,32.69,76.46,26,71.09,20.74Z"/><g id="Body"><path id="Background" d="M73,35.7c0,15.21-15.67,27.54-35,27.54S3,50.91,3,35.7C3,26.27,9,17.94,18.22,13S33.18,3,38,3s8.94,4.13,19.78,10C67,17.94,73,26.27,73,35.7Z" style="fill:#fbf0df"/><path id="Bottom_Shadow" data-name="Bottom Shadow" d="M73,35.7a21.67,21.67,0,0,0-.8-5.78c-2.73,33.3-43.35,34.9-59.32,24.94A40,40,0,0,0,38,63.24C57.3,63.24,73,50.89,73,35.7Z" style="fill:#f6dece"/><path id="Light_Shine" data-name="Light Shine" d="M24.53,11.17C29,8.49,34.94,3.46,40.78,3.45A9.29,9.29,0,0,0,38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7c0,.4,0,.8,0,1.19C9.06,15.48,20.07,13.85,24.53,11.17Z" style="fill:#fffefc"/><path id="Top" d="M35.12,5.53A16.41,16.41,0,0,1,29.49,18c-.28.25-.06.73.3.59,3.37-1.31,7.92-5.23,6-13.14C35.71,5,35.12,5.12,35.12,5.53Zm2.27,0A16.24,16.24,0,0,1,39,19c-.12.35.31.65.55.36C41.74,16.56,43.65,11,37.93,5,37.64,4.74,37.19,5.14,37.39,5.49Zm2.76-.17A16.42,16.42,0,0,1,47,17.12a.33.33,0,0,0,.65.11c.92-3.49.4-9.44-7.17-12.53C40.08,4.54,39.82,5.08,40.15,5.32ZM21.69,15.76a16.94,16.94,0,0,0,10.47-9c.18-.36.75-.22.66.18-1.73,8-7.52,9.67-11.12,9.45C21.32,16.4,21.33,15.87,21.69,15.76Z" style="fill:#ccbea7;fill-rule:evenodd"/><path id="Outline" d="M38,65.75C17.32,65.75.5,52.27.5,35.7c0-10,6.18-19.33,16.53-24.92,3-1.6,5.57-3.21,7.86-4.62,1.26-.78,2.45-1.51,3.6-2.19C32,1.89,35,.5,38,.5s5.62,1.2,8.9,3.14c1,.57,2,1.19,3.07,1.87,2.49,1.54,5.3,3.28,9,5.27C69.32,16.37,75.5,25.69,75.5,35.7,75.5,52.27,58.68,65.75,38,65.75ZM38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7,3,50.89,18.7,63.25,38,63.25S73,50.89,73,35.7C73,26.62,67.31,18.13,57.78,13,54,11,51.05,9.12,48.66,7.64c-1.09-.67-2.09-1.29-3-1.84C42.63,4,40.42,3,38,3Z"/></g><g id="Mouth"><g id="Background-2" data-name="Background"><path d="M45.05,43a8.93,8.93,0,0,1-2.92,4.71,6.81,6.81,0,0,1-4,1.88A6.84,6.84,0,0,1,34,47.71,8.93,8.93,0,0,1,31.12,43a.72.72,0,0,1,.8-.81H44.26A.72.72,0,0,1,45.05,43Z" style="fill:#b71422"/></g><g id="Tongue"><path id="Background-3" data-name="Background" d="M34,47.79a6.91,6.91,0,0,0,4.12,1.9,6.91,6.91,0,0,0,4.11-1.9,10.63,10.63,0,0,0,1-1.07,6.83,6.83,0,0,0-4.9-2.31,6.15,6.15,0,0,0-5,2.78C33.56,47.4,33.76,47.6,34,47.79Z" style="fill:#ff6164"/><path id="Outline-2" data-name="Outline" d="M34.16,47a5.36,5.36,0,0,1,4.19-2.08,6,6,0,0,1,4,1.69c.23-.25.45-.51.66-.77a7,7,0,0,0-4.71-1.93,6.36,6.36,0,0,0-4.89,2.36A9.53,9.53,0,0,0,34.16,47Z"/></g><path id="Outline-3" data-name="Outline" d="M38.09,50.19a7.42,7.42,0,0,1-4.45-2,9.52,9.52,0,0,1-3.11-5.05,1.2,1.2,0,0,1,.26-1,1.41,1.41,0,0,1,1.13-.51H44.26a1.44,1.44,0,0,1,1.13.51,1.19,1.19,0,0,1,.25,1h0a9.52,9.52,0,0,1-3.11,5.05A7.42,7.42,0,0,1,38.09,50.19Zm-6.17-7.4c-.16,0-.2.07-.21.09a8.29,8.29,0,0,0,2.73,4.37A6.23,6.23,0,0,0,38.09,49a6.28,6.28,0,0,0,3.65-1.73,8.3,8.3,0,0,0,2.72-4.37.21.21,0,0,0-.2-.09Z"/></g><g id="Face"><ellipse id="Right_Blush" data-name="Right Blush" cx="53.22" cy="40.18" rx="5.85" ry="3.44" style="fill:#febbd0"/><ellipse id="Left_Bluch" data-name="Left Bluch" cx="22.95" cy="40.18" rx="5.85" ry="3.44" style="fill:#febbd0"/><path id="Eyes" d="M25.7,38.8a5.51,5.51,0,1,0-5.5-5.51A5.51,5.51,0,0,0,25.7,38.8Zm24.77,0A5.51,5.51,0,1,0,45,33.29,5.5,5.5,0,0,0,50.47,38.8Z" style="fill-rule:evenodd"/><path id="Iris" d="M24,33.64a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,24,33.64Zm24.77,0a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,48.75,33.64Z" style="fill:#fff;fill-rule:evenodd"/></g></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

7
src/pages/Home.tsx Normal file
View File

@@ -0,0 +1,7 @@
export default function Home() {
return (
<div>
<h1>Home</h1>
</div>
);
}

84
src/pages/Login.tsx Normal file
View File

@@ -0,0 +1,84 @@
import {
Button,
Container,
Group,
Stack,
Text,
TextInput,
} from "@mantine/core";
import { useEffect, useState } from "react";
import apiFetch from "../lib/apiFetch";
import clientRoutes from "@/clientRoutes";
import { Navigate } from "react-router-dom";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
useEffect(() => {
async function checkSession() {
try {
// backend otomatis baca cookie JWT dari request
const res = await apiFetch.api.user.find.get();
setIsAuthenticated(res.status === 200);
} catch {
setIsAuthenticated(false);
}
}
checkSession();
}, []);
const handleSubmit = async () => {
setLoading(true);
try {
const response = await apiFetch.auth.login.post({
email,
password,
});
if (response.data?.token) {
localStorage.setItem("token", response.data.token);
window.location.href = clientRoutes["/sq/dashboard"];
return;
}
if (response.error) {
alert(JSON.stringify(response.error));
}
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
if (isAuthenticated === null) return null; // or loading spinner
if (isAuthenticated)
return <Navigate to={clientRoutes["/sq/dashboard"]} replace />;
return (
<Container>
<Stack>
<Text>Login</Text>
<TextInput
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<TextInput
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<Group justify="right">
<Button onClick={handleSubmit} disabled={loading}>
Login
</Button>
</Group>
</Stack>
</Container>
);
}

7
src/pages/NotFound.tsx Normal file
View File

@@ -0,0 +1,7 @@
export default function NotFound() {
return (
<div>
<h1>404 Not Found</h1>
</div>
);
}

View File

@@ -0,0 +1,327 @@
import {
Button,
Card,
Container,
Group,
Stack,
Table,
Text,
TextInput,
ScrollArea,
Divider,
Tooltip,
Badge,
Loader,
ActionIcon,
Center,
} from "@mantine/core";
import { IconKey, IconPlus, IconTrash, IconCopy } from "@tabler/icons-react";
import { useEffect, useState } from "react";
import { showNotification } from "@mantine/notifications";
import apiFetch from "@/lib/apiFetch";
export default function ApiKeyPage() {
return (
<Container
w={"100%"}
size="lg"
px="md"
py="xl"
style={{
background:
"radial-gradient(800px 400px at 10% 10%, rgba(0,255,200,0.05), transparent), radial-gradient(800px 400px at 90% 90%, rgba(0,255,255,0.04), transparent), linear-gradient(180deg, #0f0f0f 0%, #191919 100%)",
borderRadius: "20px",
boxShadow: "0 0 60px rgba(0,255,200,0.04)",
color: "#EAEAEA",
minHeight: "90vh",
}}
>
<Stack gap="xl">
<Group justify="space-between">
<Group gap="xs">
<IconKey size={28} color="#00FFC8" />
<Text fw={700} fz={26} c="#EAEAEA">
API Key Management
</Text>
</Group>
<Badge
size="lg"
radius="lg"
style={{
background:
"linear-gradient(90deg, rgba(0,255,200,0.08), rgba(0,255,255,0.05))",
border: "1px solid rgba(0,255,220,0.2)",
color: "#00FFC8",
}}
>
Secure Access
</Badge>
</Group>
<Divider color="rgba(0,255,200,0.1)" />
<CreateApiKey />
</Stack>
</Container>
);
}
function CreateApiKey() {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [expiredAt, setExpiredAt] = useState("");
const [loading, setLoading] = useState(false);
const [refresh, setRefresh] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!name.trim()) {
showNotification({
title: "Missing name",
message: "Please enter a name for your API key",
color: "red",
});
return;
}
setLoading(true);
const res = await apiFetch.api.apikey.create.post({
name,
description,
expiredAt,
});
setLoading(false);
if (res.status === 200) {
setName("");
setDescription("");
setExpiredAt("");
showNotification({
title: "Success",
message: "API key created successfully",
color: "teal",
});
setRefresh((r) => !r);
}
};
return (
<Stack gap="xl">
<Card
p="xl"
radius="lg"
style={{
background:
"linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01))",
border: "1px solid rgba(0,255,200,0.1)",
boxShadow: "0 0 30px rgba(0,255,200,0.05)",
backdropFilter: "blur(6px)",
}}
>
<Stack gap="md">
<Group justify="space-between">
<Text fw={600} fz="lg" c="#EAEAEA">
Create New API Key
</Text>
<IconPlus size={22} color="#00FFC8" />
</Group>
<form onSubmit={handleSubmit}>
<Stack gap="sm">
<TextInput
label="Key Name"
placeholder="Enter key name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<TextInput
label="Description"
placeholder="Describe the key purpose"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<TextInput
label="Expiration Date"
placeholder="YYYY-MM-DD"
type="date"
value={expiredAt}
onChange={(e) => setExpiredAt(e.target.value)}
/>
<Group justify="right" mt="md">
<Button
variant="outline"
color="gray"
onClick={() => {
setName("");
setDescription("");
setExpiredAt("");
}}
>
Clear
</Button>
<Button
type="submit"
loading={loading}
style={{
background:
"linear-gradient(90deg, #00FFC8 0%, #00FFFF 100%)",
color: "#191919",
fontWeight: 600,
}}
>
Save Key
</Button>
</Group>
</Stack>
</form>
</Stack>
</Card>
<ListApiKey refresh={refresh} />
</Stack>
);
}
function ListApiKey({ refresh }: { refresh: boolean }) {
const [apiKeys, setApiKeys] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchApiKeys = async () => {
setLoading(true);
const res = await apiFetch.api.apikey.list.get();
if (res.status === 200) {
setApiKeys(res.data?.apiKeys || []);
}
setLoading(false);
};
fetchApiKeys();
}, [refresh]);
return (
<Card
p="xl"
radius="lg"
style={{
background:
"linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01))",
border: "1px solid rgba(0,255,200,0.1)",
boxShadow: "0 0 30px rgba(0,255,200,0.05)",
backdropFilter: "blur(6px)",
}}
>
<Stack gap="md">
<Group justify="space-between">
<Text fw={600} fz="lg" c="#EAEAEA">
Active API Keys
</Text>
</Group>
<Divider color="rgba(0,255,200,0.05)" />
{loading ? (
<Center py="xl">
<Loader color="teal" />
</Center>
) : apiKeys.length === 0 ? (
<Center py="xl">
<Text c="#9A9A9A">No API keys found</Text>
</Center>
) : (
<ScrollArea>
<Table
highlightOnHover
verticalSpacing="sm"
horizontalSpacing="md"
style={{
color: "#EAEAEA",
borderCollapse: "separate",
borderSpacing: "0 8px",
}}
>
<Table.Thead>
<Table.Tr>
<Table.Th>Name</Table.Th>
<Table.Th>Description</Table.Th>
<Table.Th>Expired</Table.Th>
<Table.Th>Created</Table.Th>
<Table.Th>Updated</Table.Th>
<Table.Th align="right">Actions</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{apiKeys.map((apiKey: any, index: number) => (
<Table.Tr
key={index}
style={{
background: "rgba(255,255,255,0.02)",
borderRadius: 10,
transition: "background 0.15s ease",
}}
>
<Table.Td>{apiKey.name}</Table.Td>
<Table.Td c="#9A9A9A">
{apiKey.description || "—"}
</Table.Td>
<Table.Td>
{apiKey.expiredAt
? new Date(apiKey.expiredAt)
.toISOString()
.split("T")[0]
: "—"}
</Table.Td>
<Table.Td>
{new Date(apiKey.createdAt)
.toISOString()
.split("T")[0]}
</Table.Td>
<Table.Td>
{new Date(apiKey.updatedAt)
.toISOString()
.split("T")[0]}
</Table.Td>
<Table.Td align="right">
<Group gap={4} justify="right">
<Tooltip label="Copy Key" withArrow>
<ActionIcon
variant="light"
color="teal"
onClick={() => {
navigator.clipboard.writeText(apiKey.key);
showNotification({
title: "Copied",
message: "API key copied to clipboard",
color: "teal",
});
}}
>
<IconCopy size={18} />
</ActionIcon>
</Tooltip>
<Tooltip label="Delete Key" withArrow>
<ActionIcon
variant="light"
color="red"
onClick={async () => {
await apiFetch.api.apikey.delete.delete({
id: apiKey.id,
});
setApiKeys((prev) =>
prev.filter((a) => a.id !== apiKey.id)
);
showNotification({
title: "Deleted",
message: "API key removed successfully",
color: "red",
});
}}
>
<IconTrash size={18} />
</ActionIcon>
</Tooltip>
</Group>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea>
)}
</Stack>
</Card>
);
}

View File

@@ -0,0 +1,291 @@
import { useEffect, useState } from "react";
import {
ActionIcon,
AppShell,
Avatar,
Button,
Card,
Divider,
Flex,
Group,
NavLink,
Paper,
ScrollArea,
Stack,
Text,
Title,
Tooltip,
Badge,
} from "@mantine/core";
import { useLocalStorage } from "@mantine/hooks";
import {
IconChevronLeft,
IconChevronRight,
IconDashboard,
IconKey,
IconWebhook,
IconBrandWhatsapp,
IconUser,
IconLogout,
} from "@tabler/icons-react";
import type { User } from "generated/prisma";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import apiFetch from "@/lib/apiFetch";
import clientRoutes from "@/clientRoutes";
function Logout() {
return (
<Group justify="center" mt="md">
<Button
variant="light"
color="red"
radius="xl"
size="compact-sm"
leftSection={<IconLogout size={16} />}
onClick={async () => {
await apiFetch.auth.logout.delete();
localStorage.removeItem("token");
window.location.href = "/login";
}}
>
Logout
</Button>
</Group>
);
}
export default function DashboardLayout() {
const [opened, setOpened] = useLocalStorage({
key: "nav_open",
defaultValue: true,
});
return (
<AppShell
padding="lg"
navbar={{
width: 270,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: !opened },
}}
styles={{
main: {
background: "#191919",
color: "#EAEAEA",
},
}}
>
<AppShell.Navbar
p="md"
style={{
background: "rgba(30,30,30,0.8)",
backdropFilter: "blur(10px)",
borderRight: "1px solid rgba(0,255,200,0.15)",
// boxShadow: "0 0 18px rgba(0,255,200,0.1)",
}}
>
<AppShell.Section>
<Group justify="flex-end" p="xs">
<Tooltip
label={opened ? "Collapse navigation" : "Expand navigation"}
withArrow
color="cyan"
>
<ActionIcon
variant="light"
radius="xl"
onClick={() => setOpened((v) => !v)}
aria-label="Toggle navigation"
style={{
color: "#00FFC8",
background: "rgba(0,255,200,0.1)",
// boxShadow: "0 0 10px rgba(0,255,200,0.2)",
}}
>
{opened ? <IconChevronLeft /> : <IconChevronRight />}
</ActionIcon>
</Tooltip>
</Group>
</AppShell.Section>
<AppShell.Section grow component={ScrollArea}>
<NavigationDashboard />
</AppShell.Section>
<AppShell.Section>
<HostView />
</AppShell.Section>
</AppShell.Navbar>
<AppShell.Main>
<Stack gap="md">
<Paper
withBorder
shadow="lg"
radius="xl"
p="md"
style={{
background: "rgba(45,45,45,0.6)",
backdropFilter: "blur(8px)",
border: "1px solid rgba(0,255,200,0.2)",
}}
>
<Flex align="center" gap="md">
{!opened && (
<Tooltip label="Open navigation menu" withArrow color="cyan">
<ActionIcon
variant="light"
radius="xl"
onClick={() => setOpened(true)}
aria-label="Open navigation"
style={{
color: "#00FFFF",
background: "rgba(0,255,200,0.1)",
}}
>
<IconChevronRight />
</ActionIcon>
</Tooltip>
)}
<Title order={3} fw={600} c="#EAEAEA">
Control Center
</Title>
<Badge
variant="light"
color="teal"
size="sm"
style={{
background: "rgba(0,255,200,0.15)",
color: "#00FFFF",
}}
>
Live
</Badge>
</Flex>
</Paper>
<Outlet />
</Stack>
</AppShell.Main>
</AppShell>
);
}
function HostView() {
const [host, setHost] = useState<User | null>(null);
useEffect(() => {
async function fetchHost() {
const { data } = await apiFetch.api.user.find.get();
setHost(data?.user ?? null);
}
fetchHost();
}, []);
return (
<Card
radius="xl"
withBorder
shadow="md"
p="md"
style={{
background: "rgba(45,45,45,0.6)",
border: "1px solid rgba(0,255,200,0.15)",
// boxShadow: "0 0 12px rgba(0,255,200,0.1)",
}}
>
{host ? (
<Stack gap="sm">
<Flex gap="md" align="center">
<Avatar
size="lg"
radius="xl"
style={{
background:
"linear-gradient(145deg, rgba(0,255,200,0.3), rgba(0,255,255,0.4))",
color: "#EAEAEA",
fontWeight: 700,
}}
>
{host.name?.[0]}
</Avatar>
<Stack gap={2}>
<Text fw={600} c="#EAEAEA">
{host.name}
</Text>
<Text size="sm" c="#9A9A9A">
{host.email}
</Text>
</Stack>
</Flex>
<Divider color="rgba(0,255,200,0.2)" />
<Logout />
</Stack>
) : (
<Text size="sm" c="#9A9A9A" ta="center">
Host data unavailable
</Text>
)}
</Card>
);
}
function NavigationDashboard() {
const navigate = useNavigate();
const location = useLocation();
const items = [
{
path: "/sq/dashboard/dashboard",
label: "Overview",
icon: <IconDashboard size={20} color="#00FFFF" />,
desc: "Main dashboard insights",
},
{
path: "/sq/dashboard/apikey/apikey",
label: "API Keys",
icon: <IconKey size={20} color="#00FFFF" />,
desc: "Manage and regenerate access tokens",
},
{
path: "/sq/dashboard/wajs/wajs-home",
label: "Wajs Integration",
icon: <IconBrandWhatsapp size={20} color="#00FFFF" />,
desc: "WhatsApp session manager",
},
{
path: "/sq/dashboard/webhook/webhook-home",
label: "Webhooks",
icon: <IconWebhook size={20} color="#00FFFF" />,
desc: "Incoming and outgoing event handlers",
},
];
return (
<Stack gap="xs">
{items.map((item) => (
<NavLink
key={item.path}
active={location.pathname.startsWith(item.path)}
leftSection={item.icon}
label={item.label}
description={item.desc}
onClick={() =>
navigate(clientRoutes[item.path as keyof typeof clientRoutes])
}
style={{
borderRadius: "12px",
color: "#EAEAEA",
background: location.pathname.startsWith(item.path)
? "rgba(0,255,200,0.15)"
: "transparent",
transition: "background 0.2s ease",
}}
styles={{
label: { fontWeight: 500, color: "#EAEAEA" },
description: { color: "#9A9A9A" },
}}
/>
))}
</Stack>
);
}

View File

@@ -0,0 +1,7 @@
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
</div>
);
}

View File

@@ -0,0 +1,3 @@
export default function WajsHome() {
return <h1>Wajs Home</h1>;
}

View File

@@ -0,0 +1,48 @@
import { Navigate, Outlet } from "react-router-dom";
import useSWR from "swr";
import apiFetch from "@/lib/apiFetch";
import { Badge, Button, Chip, Group, Pill, Stack } from "@mantine/core";
import { useState } from "react";
import clientRoutes from "@/clientRoutes";
export default function WajsLayout() {
const [loading, setLoading] = useState(false);
const { data } = useSWR("/wa/qr", apiFetch.api.wa.state.get, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
refreshInterval: 3000,
onSuccess(data, key, config) {
console.log(data.data?.state);
},
});
if (!data?.data?.state) return <Outlet />;
if (data.data?.state.qr)
return <Navigate to={clientRoutes["/wajs/qrcode"]} replace />;
return (
<Stack>
<Group>
<Button
loading={loading && !data.data?.state.ready}
disabled={data.data?.state.ready}
onClick={() => {
setLoading(true);
apiFetch.api.wa.start.post();
}}
>
{data.data?.state.ready ? "Ready" : "Start"}
</Button>
<Button
onClick={() => {
setLoading(true);
apiFetch.api.wa.restart.post();
}}
>
Reconnect
</Button>
</Group>
<Outlet />
</Stack>
);
}

View File

@@ -0,0 +1,316 @@
import { useState, useMemo } from "react";
import {
Button,
Card,
Checkbox,
Group,
Stack,
Text,
TextInput,
Select,
Divider,
Title,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { IconCode, IconCheck, IconX } from "@tabler/icons-react";
import Editor from "@monaco-editor/react";
import apiFetch from "@/lib/apiFetch";
import { useNavigate } from "react-router-dom";
import clientRoutes from "@/clientRoutes";
// data.from': data.from,
// data.fromNumber': data.fromNumber,
// data.fromMe': data.fromMe,
// data.body': data.body,
// data.hasMedia': data.hasMedia,
// data.type': data.type,
// data.to': data.to,
// data.deviceType': data.deviceType,
// data.notifyName': data.notifyName,
// data.media.data': data.media?.data ?? null,
// data.media.mimetype': data.media?.mimetype ?? null,
// data.media.filename': data.media?.filename ?? null,
// data.media.filesize': data.media?.filesize ?? 0,
const templateData = `
Available variables:
{{data.from}}, {{data.fromNumber}}, {{data.fromMe}}, {{data.body}}, {{data.hasMedia}}, {{data.type}}, {{data.to}}, {{data.deviceType}}, {{data.notifyName}}, {{data.media.data}}, {{data.media.mimetype}}, {{data.media.filename}}, {{data.media.filesize}}
`;
export default function WebhookCreate() {
const navigate = useNavigate();
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [url, setUrl] = useState("");
const [method, setMethod] = useState("POST");
const [headers, setHeaders] = useState(
JSON.stringify({ "Content-Type": "application/json" }, null, 2),
);
const [payload, setPayload] = useState("{}");
const [apiToken, setApiToken] = useState("");
const [enabled, setEnabled] = useState(true);
const [replay, setReplay] = useState(false);
const [replayKey, setReplayKey] = useState("");
const safeJson = (value: string) => {
try {
return JSON.stringify(JSON.parse(value || "{}"), null, 2);
} catch {
return value || "{}";
}
};
const previewCode = useMemo(() => {
let headerObj: Record<string, string> = {};
try {
headerObj = JSON.parse(headers);
} catch { }
if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
const prettyHeaders = safeJson(JSON.stringify(headerObj));
const prettyPayload = safeJson(payload);
const includeBody = ["POST", "PUT", "PATCH"].includes(method.toUpperCase());
return `fetch("${url || "https://example.com/webhook"}", {
method: "${method}",
headers: ${prettyHeaders},${includeBody ? `\n body: ${prettyPayload},` : ""}
})
.then(res => res.json())
.then(console.log)
.catch(console.error);`;
}, [url, method, headers, payload, apiToken]);
async function onSubmit() {
const { data } = await apiFetch.api.webhook.create.post({
name,
description,
apiToken,
url,
method,
headers,
payload,
enabled,
replay,
replayKey,
});
if (data?.success) {
notifications.show({
title: "Webhook Created",
message: data.message,
color: "teal",
icon: <IconCheck />,
});
navigate(clientRoutes["/sq/dashboard/webhook"]);
} else {
notifications.show({
title: "Creation Failed",
message: data?.message || "Unable to create webhook",
color: "red",
icon: <IconX />,
});
}
}
return (
<Stack style={{ backgroundColor: "#191919" }} p="xl">
<Stack
gap="md"
maw={900}
mx="auto"
bg="rgba(45,45,45,0.6)"
p="xl"
style={{
borderRadius: "20px",
backdropFilter: "blur(12px)",
border: "1px solid rgba(0,255,200,0.2)",
// boxShadow: "0 0 25px rgba(0,255,200,0.15)",
}}
>
<Group justify="space-between">
<Title order={2} c="#EAEAEA" fw={600}>
Create Webhook
</Title>
<IconCode color="#00FFFF" size={28} />
</Group>
<Divider color="rgba(0,255,200,0.2)" />
<TextInput
label="Name"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<TextInput
label="Description"
placeholder="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<TextInput
label="Webhook URL"
placeholder="https://example.com/webhook"
value={url}
onChange={(e) => setUrl(e.target.value)}
/>
<Select
label="HTTP Method"
placeholder="Select method"
value={method}
onChange={(v) => setMethod(v || "POST")}
data={["POST", "GET", "PUT", "PATCH", "DELETE"].map((v) => ({
value: v,
label: v,
}))}
/>
<TextInput
label="API Token"
placeholder="Bearer ..."
value={apiToken}
onChange={(e) => {
setApiToken(e.target.value);
try {
const current = JSON.parse(headers);
if (!e.target.value) {
delete current["Authorization"];
} else {
current["Authorization"] = `Bearer ${e.target.value}`;
}
setHeaders(JSON.stringify(current, null, 2));
} catch { }
}}
/>
<Stack gap="xs">
<Text fw={600} c="#EAEAEA">
Headers (JSON)
</Text>
<Editor
theme="vs-dark"
height="20vh"
language="json"
value={headers}
onChange={(val) => setHeaders(val ?? "{}")}
options={{
minimap: { enabled: false },
fontSize: 13,
scrollBeyondLastLine: false,
lineNumbers: "off",
automaticLayout: true,
}}
/>
</Stack>
<Stack gap="xs">
<Text fw={600} c="#EAEAEA">
Payload
</Text>
<Text size="xs" c="#9A9A9A" mb="xs">
{templateData}
</Text>
<Editor
theme="vs-dark"
height="35vh"
language="json"
value={payload}
onChange={(val) => setPayload(val ?? "{}")}
options={{
minimap: { enabled: false },
fontSize: 13,
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</Stack>
<Checkbox
label="Enable Webhook"
checked={enabled}
onChange={(e) => setEnabled(e.currentTarget.checked)}
color="teal"
styles={{
label: { color: "#EAEAEA" },
}}
/>
<Checkbox
label="Enable Replay"
checked={replay}
onChange={(e) => setReplay(e.currentTarget.checked)}
color="teal"
styles={{
label: { color: "#EAEAEA" },
}}
/>
<TextInput
description="Replay Key is used to identify the webhook example: data.text"
label="Replay Key"
placeholder="Replay Key"
value={replayKey}
onChange={(e) => setReplayKey(e.target.value)}
/>
<Card
radius="xl"
p="md"
style={{
background: "rgba(25,25,25,0.6)",
border: "1px solid rgba(0,255,200,0.3)",
// boxShadow: "0 0 15px rgba(0,255,200,0.15)",
}}
>
<Stack gap="xs">
<Text fw={600} c="#EAEAEA">
Request Preview
</Text>
<Editor
theme="vs-dark"
height="35vh"
language="javascript"
value={previewCode}
options={{
readOnly: true,
minimap: { enabled: false },
fontSize: 13,
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</Stack>
</Card>
<Group justify="flex-end" mt="md">
<Button
onClick={() => navigate(clientRoutes["/sq/dashboard/webhook"])}
variant="subtle"
c="#EAEAEA"
styles={{
root: { backgroundColor: "#2D2D2D", borderColor: "#00FFC8" },
}}
>
Cancel
</Button>
<Button
onClick={onSubmit}
style={{
background: "linear-gradient(90deg, #00FFC8, #00FFFF)",
color: "#191919",
}}
>
Save Webhook
</Button>
</Group>
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,361 @@
import useSWR from "swr";
import apiFetch from "@/lib/apiFetch";
import { useSearchParams, useNavigate } from "react-router-dom";
import type { WebHook } from "generated/prisma";
import { useState } from "react";
import { useMemo } from "react";
import { notifications } from "@mantine/notifications";
import { IconCode, IconCheck, IconX } from "@tabler/icons-react";
import Editor from "@monaco-editor/react";
import { Stack, Group, Title, Divider, TextInput, Select, Checkbox, Card, Button, Text } from "@mantine/core";
import { modals } from "@mantine/modals";
import clientRoutes from "@/clientRoutes";
import { useShallowEffect } from "@mantine/hooks";
const templateData = `
Available variables:
{{data.from}}, {{data.fromNumber}}, {{data.fromMe}}, {{data.body}}, {{data.hasMedia}}, {{data.type}}, {{data.to}}, {{data.deviceType}}, {{data.notifyName}}, {{data.media.data}}, {{data.media.mimetype}}, {{data.media.filename}}, {{data.media.filesize}}
`;
export default function WebhookEdit() {
const [searchParams] = useSearchParams();
const id = searchParams.get("id");
const { data, error, isLoading, mutate } = useSWR("/", () => apiFetch.api.webhook.find({
id: id!
}).get(), {dedupingInterval: 3000})
const navigate = useNavigate();
useShallowEffect(() => {
mutate();
}, [data]);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!data?.data?.webhook) return <div>No data</div>;
return (
<Stack>
<Group justify="space-between">
<Title order={2}>Edit Webhook</Title>
<Button variant="outline" onClick={() => {
modals.openConfirmModal({
title: "Remove Webhook",
children: <Text>Are you sure you want to remove this webhook?</Text>,
confirmProps: { color: "red" },
labels: {
cancel: "Cancel",
confirm: "Remove",
},
onConfirm: () => {
apiFetch.api.webhook.remove({
id: id!
}).delete()
navigate(clientRoutes["/sq/dashboard/webhook"]);
},
onCancel: () => {
navigate(clientRoutes["/sq/dashboard/webhook/webhook-edit"] + "?id=" + id);
},
})
}}>Remove</Button>
</Group>
<EditView webhook={data.data?.webhook || null} />
</Stack>
);
}
function EditView({ webhook }: { webhook: WebHook | null }) {
const navigate = useNavigate();
const [name, setName] = useState(webhook?.name || "");
const [description, setDescription] = useState(webhook?.description || "");
const [url, setUrl] = useState(webhook?.url || "");
const [method, setMethod] = useState(webhook?.method || "POST");
const [headers, setHeaders] = useState(webhook?.headers || "{}");
const [payload, setPayload] = useState(webhook?.payload || "{}");
const [apiToken, setApiToken] = useState(webhook?.apiToken || "");
const [enabled, setEnabled] = useState(webhook?.enabled || true);
const [replay, setReplay] = useState(webhook?.replay || false);
const [replayKey, setReplayKey] = useState(webhook?.replayKey || "");
const safeJson = (value: string) => {
try {
return JSON.stringify(JSON.parse(value || "{}"), null, 2);
} catch {
return value || "{}";
}
};
// useShallowEffect(() => {
// let headerObj: Record<string, string> = {};
// try {
// headerObj = JSON.parse(headers);
// } catch { }
// if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
// setHeaders(JSON.stringify(headerObj, null, 2));
// }, [apiToken]);
const previewCode = useMemo(() => {
let headerObj: Record<string, string> = {};
try {
headerObj = JSON.parse(headers);
} catch { }
if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
const prettyHeaders = safeJson(JSON.stringify(headerObj));
const prettyPayload = safeJson(payload);
const includeBody = ["POST", "PUT", "PATCH"].includes(method.toUpperCase());
return `fetch("${url || "https://example.com/webhook"}", {
method: "${method}",
headers: ${prettyHeaders},${includeBody ? `\n body: ${prettyPayload},` : ""}
})
.then(res => res.json())
.then(console.log)
.catch(console.error);`;
}, [url, method, headers, payload, apiToken]);
async function onSubmit() {
if (!webhook?.id) {
return notifications.show({
title: "Webhook ID Not Found",
message: "Unable to update webhook",
color: "red",
icon: <IconX />,
});
}
const { data } = await apiFetch.api.webhook.update({
id: webhook?.id,
}).put({
name,
description,
apiToken,
url,
method,
headers,
payload,
enabled,
replay,
replayKey,
});
if (data?.success) {
notifications.show({
title: "Webhook Created",
message: data.message,
color: "teal",
icon: <IconCheck />,
});
navigate(clientRoutes["/sq/dashboard/webhook"]);
} else {
notifications.show({
title: "Creation Failed",
message: data?.message || "Unable to create webhook",
color: "red",
icon: <IconX />,
});
}
}
return (
<Stack style={{ backgroundColor: "#191919" }} p="xl">
<Stack
gap="md"
maw={900}
mx="auto"
bg="rgba(45,45,45,0.6)"
p="xl"
style={{
borderRadius: "20px",
backdropFilter: "blur(12px)",
border: "1px solid rgba(0,255,200,0.2)",
// boxShadow: "0 0 25px rgba(0,255,200,0.15)",
}}
>
<Group justify="space-between">
<Title order={2} c="#EAEAEA" fw={600}>
Create Webhook
</Title>
<IconCode color="#00FFFF" size={28} />
</Group>
<Divider color="rgba(0,255,200,0.2)" />
<TextInput
label="Name"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<TextInput
label="Description"
placeholder="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<TextInput
label="Webhook URL"
placeholder="https://example.com/webhook"
value={url}
onChange={(e) => setUrl(e.target.value)}
/>
<Select
label="HTTP Method"
placeholder="Select method"
value={method}
onChange={(v) => setMethod(v || "POST")}
data={["POST", "GET", "PUT", "PATCH", "DELETE"].map((v) => ({
value: v,
label: v,
}))}
/>
<TextInput
label="API Token"
placeholder="Bearer ..."
value={apiToken}
onChange={(e) => {
setApiToken(e.target.value);
try {
const current = JSON.parse(headers);
if (!e.target.value) {
delete current["Authorization"];
} else {
current["Authorization"] = `Bearer ${e.target.value}`;
}
setHeaders(JSON.stringify(current, null, 2));
} catch { }
}}
/>
<Stack gap="xs">
<Text fw={600} c="#EAEAEA">
Headers (JSON)
</Text>
<Editor
theme="vs-dark"
height="20vh"
language="json"
value={headers}
onChange={(val) => setHeaders(val ?? "{}")}
options={{
minimap: { enabled: false },
fontSize: 13,
scrollBeyondLastLine: false,
lineNumbers: "off",
automaticLayout: true,
}}
/>
</Stack>
<Stack gap="xs">
<Text fw={600} c="#EAEAEA">
Payload
</Text>
<Text size="xs" c="#9A9A9A" mb="xs">
{templateData}
</Text>
<Editor
theme="vs-dark"
height="35vh"
language="json"
value={payload}
onChange={(val) => setPayload(val ?? "{}")}
options={{
minimap: { enabled: false },
fontSize: 13,
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</Stack>
<Checkbox
label="Enable Webhook"
checked={enabled}
onChange={(e) => setEnabled(e.target.checked as any)}
color="teal"
styles={{
label: { color: "#EAEAEA" },
}}
/>
<Checkbox
label="Enable Replay"
checked={replay}
onChange={(e) => setReplay(e.target.checked as any)}
color="teal"
styles={{
label: { color: "#EAEAEA" },
}}
/>
<TextInput
description="Replay Key is used to identify the webhook example: data.text"
label="Replay Key"
placeholder="Replay Key"
value={replayKey}
onChange={(e) => setReplayKey(e.target.value)}
/>
<Card
radius="xl"
p="md"
style={{
background: "rgba(25,25,25,0.6)",
border: "1px solid rgba(0,255,200,0.3)",
// boxShadow: "0 0 15px rgba(0,255,200,0.15)",
}}
>
<Stack gap="xs">
<Text fw={600} c="#EAEAEA">
Request Preview
</Text>
<Editor
theme="vs-dark"
height="35vh"
language="javascript"
value={previewCode}
options={{
readOnly: true,
minimap: { enabled: false },
fontSize: 13,
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</Stack>
</Card>
<Group justify="flex-end" mt="md">
<Button
onClick={() => navigate(clientRoutes["/sq/dashboard/webhook"])}
variant="subtle"
c="#EAEAEA"
styles={{
root: { backgroundColor: "#2D2D2D", borderColor: "#00FFC8" },
}}
>
Cancel
</Button>
<Button
onClick={onSubmit}
style={{
background: "linear-gradient(90deg, #00FFC8, #00FFFF)",
color: "#191919",
}}
>
Save Webhook
</Button>
</Group>
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,249 @@
import { useMemo } from "react";
import {
Card,
Group,
Text,
Title,
Badge,
Loader,
Center,
Tooltip,
ActionIcon,
Stack,
Divider,
Button,
} from "@mantine/core";
import {
IconLink,
IconCode,
IconKey,
IconCheck,
IconX,
IconRefresh,
IconEdit,
IconPlus,
IconMessageReply,
} from "@tabler/icons-react";
import { notifications } from "@mantine/notifications";
import useSWR from "swr";
import apiFetch from "@/lib/apiFetch";
import { useNavigate } from "react-router-dom";
import clientRoutes from "@/clientRoutes";
import { useShallowEffect } from "@mantine/hooks";
export default function WebhookHome() {
const navigate = useNavigate();
const { data, error, isLoading, mutate } = useSWR(
"/",
apiFetch.api.webhook.list.get, { dedupingInterval: 3000, refreshInterval: 3000 });
const webhooks = useMemo(() => data?.data?.list ?? [], [data]);
useShallowEffect(() => {
mutate();
}, []);
function ButtonCreate() {
return <Tooltip label="Create new webhook" withArrow color="teal">
<Button
radius="xl"
size="md"
leftSection={<IconPlus size={18} />}
variant="gradient"
gradient={{ from: "#00FFC8", to: "#00FFFF", deg: 135 }}
style={{
color: "#191919",
fontWeight: 600,
// boxShadow: "0 0 12px rgba(0,255,200,0.25)",
transition: "transform 0.2s ease, box-shadow 0.2s ease",
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = "translateY(-2px)";
e.currentTarget.style.boxShadow =
"0 0 20px rgba(0,255,200,0.4)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = "translateY(0)";
e.currentTarget.style.boxShadow =
"0 0 12px rgba(0,255,200,0.25)";
}}
onClick={() => navigate("/sq/dashboard/webhook/webhook-create")}
>
Create Webhook
</Button>
</Tooltip>
}
if (isLoading)
return (
<Center h="100vh" bg="#191919">
<Loader color="teal" size="lg" />
</Center>
);
if (error)
return (
<Center h="100vh" bg="#191919">
<Text c="#FF4B4B" fw={500}>
Failed to load webhooks. Please try again.
</Text>
</Center>
);
if (!webhooks.length)
return (
<Center h="100vh" bg="#191919">
<Stack align="center" gap="sm">
<Text c="#9A9A9A" size="lg">
No webhooks found
</Text>
<Text c="#00FFC8" size="sm">
Connect your first webhook to start managing events
</Text>
<ButtonCreate />
</Stack>
</Center>
);
return (
<Stack style={{ backgroundColor: "#191919" }} p="xl">
<Group justify="space-between" mb="lg">
<Title order={2} c="#EAEAEA" fw={600}>
Webhook Manager
</Title>
<ButtonCreate />
<Tooltip label="Refresh webhooks" withArrow color="cyan">
<ActionIcon
variant="light"
size="lg"
radius="xl"
onClick={() => {
mutate();
notifications.show({
title: "Refreshing data",
message: "Webhook list is being updated...",
color: "teal",
});
}}
>
<IconRefresh color="#00FFFF" />
</ActionIcon>
</Tooltip>
</Group>
<Stack gap="md">
{webhooks.map((webhook) => (
<Card
key={webhook.id}
p="lg"
radius="xl"
style={{
background: "rgba(45,45,45,0.6)",
backdropFilter: "blur(12px)",
border: "1px solid rgba(0,255,200,0.2)",
// boxShadow: "0 0 12px rgba(0,255,200,0.15)",
transition: "transform 0.2s ease, box-shadow 0.2s ease",
}}
>
<Group justify="end" mb="sm">
<Group>
<IconLink color="#00FFFF" />
<Text c="#EAEAEA" fw={500} size="lg">
{webhook.name}
</Text>
</Group>
<ActionIcon
c={"teal"}
variant="light"
size="lg"
radius="xl"
onClick={() => navigate(`${clientRoutes["/sq/dashboard/webhook/webhook-edit"]}?id=${webhook.id}`)}
>
<IconEdit />
</ActionIcon>
</Group>
<Stack gap={"md"}>
<Group>
<Badge
color={webhook.enabled ? "teal" : "red"}
radius="xl"
leftSection={
webhook.enabled ? (
<IconCheck size={14} />
) : (
<IconX size={14} />
)
}
>
{webhook.enabled ? "Active" : "Disabled"}
</Badge>
<Badge bg={"teal"} leftSection={<IconMessageReply size={16} color="#00FFC8" />}>
{webhook.replay ? "Replay" : "Not Replay"}
</Badge>
</Group>
<Text c="#9A9A9A" size="sm">{webhook.description}</Text>
</Stack>
<Divider color="rgba(0,255,200,0.2)" my="sm" />
<Stack gap="xs">
<Group gap="xs">
<IconCode size={16} color="#00FFC8" />
<Text c="#9A9A9A" size="sm">
Method:
</Text>
<Text c="#EAEAEA" size="sm" fw={500}>
{webhook.method}
</Text>
</Group>
<Group gap="xs">
<IconLink size={16} color="#00FFC8" />
<Text c="#9A9A9A" size="sm">
URL:
</Text>
<Text c="#EAEAEA" size="sm" fw={500}>
{webhook.url}
</Text>
</Group>
<Group gap="xs">
<IconKey size={16} color="#00FFC8" />
<Text c="#9A9A9A" size="sm">
API Token:
</Text>
<Text c="#EAEAEA" size="sm" fw={500}>
{webhook.apiToken?.slice(0, 6) + "..." || "—"}
</Text>
</Group>
<Group gap="xs">
<Text c="#9A9A9A" size="sm">
Headers:
</Text>
<Text c="#EAEAEA" size="sm" fw={500}>
{Object.keys(webhook.headers || {}).length
? webhook.headers
: "No headers configured"}
</Text>
</Group>
<Group gap="xs">
<Text c="#9A9A9A" size="sm">
Payload:
</Text>
<Text c="#EAEAEA" size="sm" fw={500}>
{Object.keys(webhook.payload || {}).length
? webhook.payload
: "Empty payload"}
</Text>
</Group>
</Stack>
</Card>
))}
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,20 @@
import {
Button,
Group,
Stack,
Title,
Tooltip,
Divider,
Container,
Paper,
} from "@mantine/core";
import { IconPlus } from "@tabler/icons-react";
import { useNavigate, Outlet } from "react-router-dom";
export default function WebhookLayout() {
const navigate = useNavigate();
return (
<Outlet />
);
}

View File

@@ -0,0 +1,25 @@
import { useEffect, useState } from "react";
import { Navigate, Outlet } from "react-router-dom";
import clientRoutes from "@/clientRoutes";
import apiFetch from "@/lib/apiFetch";
export default function ProtectedRoute() {
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
useEffect(() => {
async function checkSession() {
try {
// backend otomatis baca cookie JWT dari request
const res = await apiFetch.api.user.find.get();
setIsAuthenticated(res.status === 200);
} catch {
setIsAuthenticated(false);
}
}
checkSession();
}, []);
if (isAuthenticated === null) return null; // or loading spinner
if (!isAuthenticated) return <Navigate to={clientRoutes["/login"]} replace />;
return <Outlet />;
}

22
src/pages/wajs/qrcode.tsx Normal file
View File

@@ -0,0 +1,22 @@
import apiFetch from "@/lib/apiFetch";
import { ReactQRCode } from "@lglab/react-qr-code";
import { Card, Container, Group } from "@mantine/core";
import useSWR from "swr";
export default function QrcodePage() {
const { data } = useSWR("/wa/qr", apiFetch.api.wa.qr.get, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
refreshInterval: 3000,
});
return (
<Container size={"sm"}>
<h1>QrCode</h1>
<Group>
<Card bg={"white"}>
<ReactQRCode size={256} value={data?.data?.qr || ""} />
</Card>
</Group>
</Container>
);
}

8
src/react.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348">
<circle cx="0" cy="0" r="2.05" fill="#61dafb"/>
<g stroke="#61dafb" stroke-width="1" fill="none">
<ellipse rx="11" ry="4.2"/>
<ellipse rx="11" ry="4.2" transform="rotate(60)"/>
<ellipse rx="11" ry="4.2" transform="rotate(120)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@@ -0,0 +1,51 @@
/**
* Helper type to recursively generate all possible key paths up to 7 levels deep.
* Example: "media.data", "a.b.c.d.e.f.g"
*/
type NestedKeyOf<T, Prev extends string = ''> = {
[K in keyof T & (string | number)]: T[K] extends Record<string, any>
? | `${Prev}${K}`
| `${Prev}${K}.${NestedKeyOf<T[K], ''>}`
: `${Prev}${K}`;
}[keyof T & (string | number)];
/**
* Safely get deep value by string path like "a.b.c[0].d"
*/
export function getValueByPath<
T extends object,
P extends string,
R = unknown
>(obj: T, path: P, defaultValue?: R): any {
try {
return path
.replace(/\[(\w+)\]/g, '.$1')
.split('.')
.reduce((acc: any, key) => (acc != null ? acc[key] : undefined), obj) ?? defaultValue;
} catch {
return defaultValue;
}
}
/**
* Safely set deep value by string path like "a.b.c[0].d"
*/
export function setValueByPath<T extends object>(
obj: T,
path: string,
value: any
): void {
const keys = path.replace(/\[(\w+)\]/g, '.$1').split('.');
let current: any = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (current[key as keyof typeof current] == null || typeof current[key as keyof typeof current] !== 'object') {
current[key as keyof typeof current] = isNaN(Number(keys[i + 1])) ? {} : [];
}
current = current[key as keyof typeof current];
}
current[keys[keys.length - 1] as keyof typeof current] = value;
}

11
src/server/lib/prisma.ts Normal file
View File

@@ -0,0 +1,11 @@
import { PrismaClient } from 'generated/prisma'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}

View File

@@ -0,0 +1,343 @@
import WAWebJS, { Client, LocalAuth, MessageMedia } from 'whatsapp-web.js';
import qrcode from 'qrcode-terminal';
import fs from 'fs/promises';
import path from 'path';
import { v4 as uuid } from 'uuid';
import { prisma } from '../prisma';
import { getValueByPath } from '../get_value_by_path';
// === KONFIGURASI UTAMA ===
const MEDIA_DIR = path.join(process.cwd(), 'downloads');
await ensureDir(MEDIA_DIR);
type DataMessage = {
from: string;
fromNumber: string;
fromMe: boolean;
body: string;
hasMedia: boolean;
type: WAWebJS.MessageTypes;
to: string;
deviceType: string;
media: {
data: WAWebJS.MessageMedia["data"];
mimetype: WAWebJS.MessageMedia["mimetype"];
filename: WAWebJS.MessageMedia["filename"];
filesize: WAWebJS.MessageMedia["filesize"];
};
notifyName: string;
}
// === STATE GLOBAL ===
const state = {
client: null as Client | null,
reconnectTimeout: null as NodeJS.Timeout | null,
isReconnecting: false,
isStarting: false,
qr: null as string | null,
ready: false,
async restart() {
log('🔄 Restart manual diminta...');
await destroyClient();
await startClient();
},
async forceStart() {
log('⚠️ Force start — menghapus cache dan session auth...');
await destroyClient();
await safeRm("./.wwebjs_auth");
await safeRm("./wwebjs_cache");
await startClient();
},
async stop() {
log('🛑 Stop manual diminta...');
await destroyClient();
},
};
// === UTIL ===
function log(...args: any[]) {
console.log(`[${new Date().toISOString()}]`, ...args);
}
async function ensureDir(dir: string) {
try {
await fs.access(dir);
} catch {
await fs.mkdir(dir, { recursive: true });
}
}
async function safeRm(path: string) {
try {
await fs.rm(path, { recursive: true, force: true });
} catch (err) {
log(`⚠️ Gagal hapus ${path}:`, err);
}
}
// === CLEANUP CLIENT ===
async function destroyClient() {
if (state.reconnectTimeout) {
clearTimeout(state.reconnectTimeout);
state.reconnectTimeout = null;
}
if (state.client) {
try {
state.client.removeAllListeners();
await state.client.destroy();
log('🧹 Client lama dihentikan & listener dibersihkan');
} catch (err) {
log('⚠️ Gagal destroy client:', err);
}
state.client = null;
state.ready = false;
}
}
// === PEMBUATAN CLIENT ===
async function startClient() {
if (state.isStarting || state.isReconnecting) {
log('⏳ startClient diabaikan — proses sedang berjalan...');
return;
}
state.isStarting = true;
await destroyClient();
log('🚀 Memulai WhatsApp client...');
const client = new Client({
authStrategy: new LocalAuth({
dataPath: path.join(process.cwd(), '.wwebjs_auth'),
}),
puppeteer: {
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
],
},
});
state.client = client;
// === EVENT LISTENERS ===
client.on('qr', (qr) => {
state.qr = qr;
qrcode.generate(qr, { small: true });
log('🔑 QR code baru diterbitkan');
});
client.on('ready', () => {
log('✅ WhatsApp client siap digunakan!');
state.ready = true;
state.isReconnecting = false;
state.isStarting = false;
state.qr = null;
if (state.reconnectTimeout) {
clearTimeout(state.reconnectTimeout);
state.reconnectTimeout = null;
}
});
client.on('auth_failure', (msg) => {
log('❌ Autentikasi gagal:', msg);
state.ready = false;
});
client.on('disconnected', async (reason) => {
log('⚠️ Client terputus:', reason);
state.ready = false;
if (state.reconnectTimeout) clearTimeout(state.reconnectTimeout);
log('⏳ Mencoba reconnect dalam 5 detik...');
state.reconnectTimeout = setTimeout(async () => {
state.isReconnecting = false;
await startClient();
}, 5000);
});
client.on('message', handleIncomingMessage);
// === INISIALISASI ===
try {
await client.initialize();
} catch (err) {
log('❌ Gagal inisialisasi client:', err);
log('⏳ Mencoba reconnect dalam 10 detik...');
state.reconnectTimeout = setTimeout(async () => {
state.isReconnecting = false;
await startClient();
}, 10000);
} finally {
state.isStarting = false;
}
}
// === HANDLER PESAN MASUK ===
async function handleIncomingMessage(msg: WAWebJS.Message) {
log(`💬 Pesan dari ${msg.from}: ${msg.body || '[MEDIA]'}`);
if (msg.from.endsWith('@g.us') || msg.isStatus || msg.from === 'status@broadcast') {
log(`🚫 Pesan dari grup/status diabaikan (${msg.from})`);
return;
}
try {
const body = msg.body?.toLowerCase().trim() || '';
const notifyName = (msg as any)._data.notifyName;
const dataMessage: DataMessage = {
from: msg.from,
fromNumber: msg.from.split('@')[0] || '',
fromMe: msg.fromMe,
body: msg.body,
hasMedia: msg.hasMedia,
type: msg.type,
to: msg.to,
deviceType: msg.deviceType,
media: {
data: null as unknown as WAWebJS.MessageMedia['data'],
mimetype: null as unknown as WAWebJS.MessageMedia['mimetype'],
filename: null as unknown as WAWebJS.MessageMedia['filename'],
filesize: null as unknown as WAWebJS.MessageMedia['filesize'],
},
notifyName,
};
// Media handler
if (msg.hasMedia) {
const media = await msg.downloadMedia();
dataMessage.media = {
data: media.data,
mimetype: media.mimetype,
filename: media.filename,
filesize: media.filesize
};
}
// to web hook
try {
const webhooks = await prisma.webHook.findMany({ where: { enabled: true } });
if (!webhooks.length) {
log('🚫 Tidak ada webhook yang aktif');
return;
}
await Promise.allSettled(
webhooks.map(async (hook) => {
try {
console.log("send webhook " + hook.url);
const body = payloadConverter({ payload: hook.payload ?? JSON.stringify(dataMessage), data: dataMessage });
const res = await fetch(hook.url, {
method: hook.method,
headers: {
...(JSON.parse(hook.headers ?? '{}') as Record<string, string>),
...(hook.apiToken ? { Authorization: `Bearer ${hook.apiToken}` } : {}),
},
body,
});
if (!res.ok) log(`⚠️ Webhook ${hook.url} gagal: ${res.status}`);
const responseJson = await res.json();
if (hook.replay) {
try {
// === Simulasikan sedang mengetik ===
const chat = await msg.getChat();
await chat.sendStateTyping(); // tampilkan status 'sedang mengetik...'
// Durasi delay tergantung panjang teks (lebih panjang = lebih lama)
const textResponseRaw = hook.replayKey
? getValueByPath(responseJson, hook.replayKey, JSON.stringify(responseJson))
: JSON.stringify(responseJson, null, 2);
const typingDelay = Math.min(5000, Math.max(1500, textResponseRaw.length * 20)); // 1.55 detik
await new Promise((resolve) => setTimeout(resolve, typingDelay));
// Setelah delay, hentikan typing indicator
await chat.clearState(); // hilangkan status "mengetik..."
// Kirim balasan ke pengirim
await msg.reply(textResponseRaw);
log(`💬 Balasan dikirim ke ${msg.from} setelah mengetik selama ${typingDelay}ms`);
} catch (err) {
log('⚠️ Gagal menampilkan status mengetik:', err);
await msg.reply(hook.replayKey
? getValueByPath(responseJson, hook.replayKey, JSON.stringify(responseJson))
: JSON.stringify(responseJson, null, 2)
);
}
}
} catch (err) {
log(`❌ Gagal kirim ke ${hook.url}:`, err);
}
})
);
} catch (error) {
console.log(error);
}
} catch (err) {
log('❌ Error handling pesan:', err);
}
}
function payloadConverter({ payload, data }: { payload: string; data: DataMessage }) {
try {
const map: Record<string, string | number | boolean | null> = {
'data.from': data.from,
'data.fromNumber': data.fromNumber,
'data.fromMe': data.fromMe,
'data.body': data.body,
'data.hasMedia': data.hasMedia,
'data.type': data.type,
'data.to': data.to,
'data.deviceType': data.deviceType,
'data.notifyName': data.notifyName,
'data.media.data': data.media?.data ?? null,
'data.media.mimetype': data.media?.mimetype ?? null,
'data.media.filename': data.media?.filename ?? null,
'data.media.filesize': data.media?.filesize ?? 0,
};
let result = payload;
for (const [key, value] of Object.entries(map)) {
result = result.replace(new RegExp(`{{\\s*${key}\\s*}}`, 'g'), String(value ?? ''));
}
return result;
} catch {
return JSON.stringify(data, null, 2);
}
}
// === CLEANUP SAAT EXIT ===
process.on('SIGINT', async () => {
log('🛑 SIGINT diterima, menutup client...');
await destroyClient();
process.exit(0);
});
process.on('SIGTERM', async () => {
log('🛑 SIGTERM diterima, menutup client...');
await destroyClient();
process.exit(0);
});
const getState = () => state;
export { startClient, destroyClient, getState };
if (import.meta.main) {
await startClient();
}

View File

@@ -0,0 +1,54 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import jwt, { type JWTPayloadSpec } from '@elysiajs/jwt'
import Elysia from 'elysia'
import { prisma } from '../lib/prisma'
const secret = process.env.JWT_SECRET
export default function apiAuth(app: Elysia) {
if (!secret) {
throw new Error('JWT_SECRET is not defined')
}
return app
.use(
jwt({
name: 'jwt',
secret,
})
)
.derive(async ({ cookie, headers, jwt }) => {
let token: string | undefined
if (cookie?.token?.value) {
token = cookie.token.value as any
}
if (headers['x-token']?.startsWith('Bearer ')) {
token = (headers['x-token'] as string).slice(7)
}
if (headers['authorization']?.startsWith('Bearer ')) {
token = (headers['authorization'] as string).slice(7)
}
let user: null | Awaited<ReturnType<typeof prisma.user.findUnique>> = null
if (token) {
try {
const decoded = (await jwt.verify(token)) as JWTPayloadSpec
if (decoded.sub) {
user = await prisma.user.findUnique({
where: { id: decoded.sub as string },
})
}
} catch (err) {
console.warn('[SERVER][apiAuth] Invalid token', err)
}
}
return { user }
})
.onBeforeHandle(({ user, set }) => {
if (!user) {
set.status = 401
return { error: 'Unauthorized' }
}
})
}

View File

@@ -0,0 +1,105 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type JWTPayloadSpec } from '@elysiajs/jwt'
import Elysia, { t } from 'elysia'
import { type User } from 'generated/prisma'
import { prisma } from '../lib/prisma'
const NINETY_YEARS = 60 * 60 * 24 * 365 * 90 // in seconds
type JWT = {
sign(data: Record<string, string | number> & JWTPayloadSpec): Promise<string>
verify(
jwt?: string
): Promise<false | (Record<string, string | number> & JWTPayloadSpec)>
}
const ApiKeyRoute = new Elysia({
prefix: '/apikey',
detail: { tags: ['apikey'] },
})
.post(
'/create',
async ctx => {
const { user }: { user: User } = ctx as any
const { name, description, expiredAt } = ctx.body
const { sign } = (ctx as any).jwt as JWT
// hitung expiredAt
const exp = expiredAt
? Math.floor(new Date(expiredAt).getTime() / 1000) // jika dikirim
: Math.floor(Date.now() / 1000) + NINETY_YEARS // default 90 tahun
const token = await sign({
sub: user.id,
aud: 'host',
exp,
payload: JSON.stringify({
name,
description,
expiredAt,
}),
})
const apiKey = await prisma.apiKey.create({
data: {
name,
description,
key: token,
userId: user.id,
expiredAt: new Date(exp * 1000), // simpan juga di DB biar gampang query
},
})
return { message: 'success', token, apiKey }
},
{
detail: {
summary: 'create api key',
},
body: t.Object({
name: t.String(),
description: t.String(),
expiredAt: t.Optional(t.String({ format: 'date-time' })), // ISO date string
}),
}
)
.get(
'/list',
async ctx => {
const { user }: { user: User } = ctx as any
const apiKeys = await prisma.apiKey.findMany({
where: {
userId: user.id,
},
})
return { message: 'success', apiKeys }
},
{
detail: {
summary: 'get api key list',
},
}
)
.delete(
'/delete',
async ctx => {
const { id } = ctx.body as { id: string }
const apiKey = await prisma.apiKey.delete({
where: {
id,
},
})
return { message: 'success', apiKey }
},
{
detail: {
summary: 'delete api key',
},
body: t.Object({
id: t.String(),
}),
}
)
export default ApiKeyRoute

View File

@@ -0,0 +1,155 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { jwt as jwtPlugin, type JWTPayloadSpec } from '@elysiajs/jwt'
import Elysia, { t, type Cookie, type HTTPHeaders, type StatusMap } from 'elysia'
import { type ElysiaCookie } from 'elysia/cookies'
import { prisma } from '@/server/lib/prisma'
import type { User } from 'generated/prisma'
const secret = process.env.JWT_SECRET
if (!secret) {
throw new Error('Missing JWT_SECRET in environment variables')
}
const isProd = process.env.NODE_ENV === 'production'
const NINETY_YEARS = 60 * 60 * 24 * 365 * 90
type JWT = {
sign(data: Record<string, string | number> & JWTPayloadSpec): Promise<string>
verify(
jwt?: string
): Promise<false | (Record<string, string | number> & JWTPayloadSpec)>
}
type COOKIE = Record<string, Cookie<string | undefined>>
type SET = {
headers: HTTPHeaders
status?: number | keyof StatusMap
redirect?: string
cookie?: Record<string, ElysiaCookie>
}
async function issueToken({
jwt,
cookie,
userId,
role,
expiresAt,
}: {
jwt: JWT
cookie: COOKIE
userId: string
role: 'host' | 'user'
expiresAt: number
}) {
const token = await jwt.sign({
sub: userId,
aud: role,
exp: expiresAt,
})
cookie.token?.set({
value: token,
httpOnly: true,
secure: isProd, // aktifkan hanya di production (HTTPS)
sameSite: 'strict',
maxAge: NINETY_YEARS,
path: '/',
})
return token
}
async function login({
body,
cookie,
set,
jwt,
}: {
body: { email: string; password: string }
cookie: COOKIE
set: SET
jwt: JWT
}) {
try {
const { email, password } = body
const user = await prisma.user.findUnique({
where: { email },
})
if (!user) {
set.status = 401
return { message: 'User not found' }
}
if (user.password !== password) {
set.status = 401
return { message: 'Invalid password' }
}
const token = await issueToken({
jwt,
cookie,
userId: user.id,
role: 'user',
expiresAt: Math.floor(Date.now() / 1000) + NINETY_YEARS,
})
return { token }
} catch (error) {
console.error('Error logging in:', error)
return {
message: 'Login failed',
error:
error instanceof Error ? error.message : JSON.stringify(error ?? null),
}
}
}
const Auth = new Elysia({
prefix: '/auth',
detail: { description: 'Auth API', summary: 'Auth API', tags: ['auth'] },
})
.use(
jwtPlugin({
name: 'jwt',
secret,
})
)
.post(
'/login',
async ({ jwt, body, cookie, set }) => {
return await login({
jwt: jwt as JWT,
body,
cookie: cookie as any,
set: set as any,
})
},
{
body: t.Object({
email: t.String(),
password: t.String(),
}),
detail: {
description: 'Login with phone; auto-register if not found',
summary: 'login',
},
}
)
.delete(
'/logout',
({ cookie }) => {
cookie.token?.remove()
return { message: 'Logout successful' }
},
{
detail: {
description: 'Logout (clear token cookie)',
summary: 'logout',
},
}
)
export default Auth

View File

@@ -0,0 +1,8 @@
import Elysia from "elysia";
const Dashboard = new Elysia({
prefix: "/dashboard"
})
.get("/apa", () => "Hello World")
export default Dashboard

View File

@@ -0,0 +1,52 @@
import Elysia from "elysia";
import { startClient, getState } from "../lib/wa/wa_service";
import _ from "lodash";
const WaRoute = new Elysia({
prefix: "/wa",
tags: ["WhatsApp"]
})
.post("/start", async () => {
startClient();
return {
message: "WhatsApp route started",
};
})
.get("/qr", () => {
const state = getState();
return {
qr: state.qr,
};
})
.get("/ready", () => {
const state = getState();
return {
ready: state.ready,
};
})
.post("/restart", async () => {
getState().restart();
return {
message: "WhatsApp route restarted",
};
})
.post("/force-start", async () => {
getState().forceStart();
return {
message: "WhatsApp route force started",
};
})
.post("/stop", async () => {
getState().stop();
return {
message: "WhatsApp route stopped",
};
})
.get("/state", () => {
const state = getState();
return {
state: _.omit(state, "client"),
};
})
export default WaRoute;

View File

@@ -0,0 +1,147 @@
import Elysia, { t } from "elysia";
import { prisma } from "../lib/prisma";
const WebhookRoute = new Elysia({
prefix: "/webhook",
tags: ["Webhook"]
})
.post("/create", async (ctx) => {
const { name, description, url, method, headers, payload, apiToken, enabled, replay, replayKey } = ctx.body;
await prisma.webHook.create({
data: {
name,
description,
url,
method,
headers: headers,
payload: payload,
apiToken,
enabled,
replay,
replayKey,
},
});
return {
success: true,
message: "Webhook route created",
};
}, {
body: t.Object({
name: t.String(),
description: t.String(),
url: t.String(),
method: t.String(),
headers: t.String(),
payload: t.String(),
apiToken: t.String(),
enabled: t.Boolean(),
replay: t.Boolean(),
replayKey: t.String(),
}),
detail: {
summary: "Create webhook",
description: "Create webhook route with live preview code",
},
})
.get("/list", async (ctx) => {
const webhooks = await prisma.webHook.findMany();
return {
list: webhooks,
};
}, {
detail: {
summary: "List webhooks",
description: "List all webhooks",
},
})
.get("/find/:id", async (ctx: { params: { id: string } }) => {
const webhook = await prisma.webHook.findUnique({
where: {
id: ctx.params.id,
},
});
return {
webhook,
};
}, {
params: t.Object({
id: t.String(),
}),
detail: {
summary: "Find webhook",
description: "Find webhook by id",
},
})
.delete("/remove/:id", async (ctx: { params: { id: string } }) => {
await prisma.webHook.delete({
where: {
id: ctx.params.id,
},
});
return {
success: true,
message: "Webhook route removed",
};
}, {
params: t.Object({
id: t.String(),
}),
detail: {
summary: "Remove webhook",
description: "Remove webhook by id",
},
})
.put("/update/:id", async (ctx) => {
const { name, description, url, method, headers, payload, apiToken, enabled, replay, replayKey } = ctx.body;
await prisma.webHook.update({
where: {
id: ctx.params.id,
},
data: {
name,
description,
url,
method,
headers: headers,
payload: payload,
apiToken,
enabled,
replay,
replayKey,
},
});
return {
success: true,
message: "Webhook route updated",
};
}, {
params: t.Object({
id: t.String(),
}),
body: t.Object({
name: t.String(),
description: t.String(),
url: t.String(),
method: t.String(),
headers: t.String(),
payload: t.String(),
apiToken: t.String(),
enabled: t.Boolean(),
replay: t.Boolean(),
replayKey: t.String(),
}),
detail: {
summary: "Update webhook",
description: "Update webhook by id",
},
})
.onError((ctx) => {
console.log(ctx.error);
return {
success: false,
message: ctx.error,
};
});
export default WebhookRoute;

36
tsconfig.json Normal file
View File

@@ -0,0 +1,36 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
},
"exclude": ["dist", "node_modules"]
}

8
types/env.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL?: string;
JWT_SECRET?: string;
BUN_PUBLIC_BASE_URL?: string;
PORT?: string;
}
}

73
x.ts Normal file
View File

@@ -0,0 +1,73 @@
/**
* Helper type to recursively generate all possible key paths up to 7 levels deep.
* Example: "media.data", "a.b.c.d.e.f.g"
*/
type NestedKeyOf<T, Prev extends string = ''> = {
[K in keyof T & (string | number)]: T[K] extends Record<string, any>
? | `${Prev}${K}`
| `${Prev}${K}.${NestedKeyOf<T[K], ''>}`
: `${Prev}${K}`;
}[keyof T & (string | number)];
/**
* Safely get deep value by string path like "a.b.c[0].d"
*/
function getValueByPath<
T extends object,
P extends string,
R = unknown
>(obj: T, path: P, defaultValue?: R): any {
try {
return path
.replace(/\[(\w+)\]/g, '.$1')
.split('.')
.reduce((acc: any, key) => (acc != null ? acc[key] : undefined), obj) ?? defaultValue;
} catch {
return defaultValue;
}
}
/**
* Safely set deep value by string path like "a.b.c[0].d"
*/
export function setValueByPath<T extends object>(
obj: T,
path: string,
value: any
): void {
const keys = path.replace(/\[(\w+)\]/g, '.$1').split('.');
let current: any = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (current[key as keyof typeof current] == null || typeof current[key as keyof typeof current] !== 'object') {
current[key as keyof typeof current] = isNaN(Number(keys[i + 1])) ? {} : [];
}
current = current[key as keyof typeof current];
}
current[keys[keys.length - 1] as keyof typeof current] = value;
}
const data = {
"from": "6289505046093@c.us",
"fromMe": false,
"body": "halo gaes",
"hasMedia": false,
"type": "chat",
"to": "6289697338821@c.us",
"deviceType": "android",
"media": {
"data": "image...",
"mimetype": null,
"filename": null,
"filesize": null
},
"notifyName": "jenna ai"
}
const find = getValueByPath(data, "media.data")
console.log(find)

23
x.txt Normal file
View File

@@ -0,0 +1,23 @@
ini adalah response dari fetch api
{
"from": "6289505046093@c.us",
"fromMe": false,
"body": "halo gaes",
"hasMedia": false,
"type": "chat",
"to": "6289697338821@c.us",
"deviceType": "android",
"media": {
"data": null,
"mimetype": null,
"filename": null,
"filesize": null
},
"notifyName": "jenna ai"
}
saya ingin mengambil data dynamic dari response tersebut menggunakan string
function getData(responseJson, key){
}