tambahannya
This commit is contained in:
4
.env.example
Normal file
4
.env.example
Normal 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
34
.gitignore
vendored
Normal 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
90
README.md
Normal 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
17
bun-env.d.ts
vendored
Normal 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
571
bun.lock
Normal 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
2
bunfig.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[serve.static]
|
||||
env = "BUN_PUBLIC_*"
|
||||
16
downloads/dataMessage.json
Normal file
16
downloads/dataMessage.json
Normal 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
187
downloads/fullChat.json
Normal 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
205
downloads/media.json
Normal 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
1
generated/prisma/client.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./index"
|
||||
4
generated/prisma/client.js
Normal file
4
generated/prisma/client.js
Normal 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
1
generated/prisma/default.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./index"
|
||||
4
generated/prisma/default.js
Normal file
4
generated/prisma/default.js
Normal 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
1
generated/prisma/edge.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./default"
|
||||
224
generated/prisma/edge.js
Normal file
224
generated/prisma/edge.js
Normal file
File diff suppressed because one or more lines are too long
211
generated/prisma/index-browser.js
Normal file
211
generated/prisma/index-browser.js
Normal 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
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
245
generated/prisma/index.js
Normal file
File diff suppressed because one or more lines are too long
BIN
generated/prisma/libquery_engine-darwin-arm64.dylib.node
Executable file
BIN
generated/prisma/libquery_engine-darwin-arm64.dylib.node
Executable file
Binary file not shown.
183
generated/prisma/package.json
Normal file
183
generated/prisma/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
generated/prisma/query_engine_bg.js
Normal file
2
generated/prisma/query_engine_bg.js
Normal file
File diff suppressed because one or more lines are too long
BIN
generated/prisma/query_engine_bg.wasm
Normal file
BIN
generated/prisma/query_engine_bg.wasm
Normal file
Binary file not shown.
34
generated/prisma/runtime/edge-esm.js
Normal file
34
generated/prisma/runtime/edge-esm.js
Normal file
File diff suppressed because one or more lines are too long
34
generated/prisma/runtime/edge.js
Normal file
34
generated/prisma/runtime/edge.js
Normal file
File diff suppressed because one or more lines are too long
370
generated/prisma/runtime/index-browser.d.ts
vendored
Normal file
370
generated/prisma/runtime/index-browser.d.ts
vendored
Normal 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 { }
|
||||
16
generated/prisma/runtime/index-browser.js
Normal file
16
generated/prisma/runtime/index-browser.js
Normal file
File diff suppressed because one or more lines are too long
3977
generated/prisma/runtime/library.d.ts
vendored
Normal file
3977
generated/prisma/runtime/library.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
146
generated/prisma/runtime/library.js
Normal file
146
generated/prisma/runtime/library.js
Normal file
File diff suppressed because one or more lines are too long
83
generated/prisma/runtime/react-native.js
vendored
Normal file
83
generated/prisma/runtime/react-native.js
vendored
Normal file
File diff suppressed because one or more lines are too long
84
generated/prisma/runtime/wasm-compiler-edge.js
Normal file
84
generated/prisma/runtime/wasm-compiler-edge.js
Normal file
File diff suppressed because one or more lines are too long
36
generated/prisma/runtime/wasm-engine-edge.js
Normal file
36
generated/prisma/runtime/wasm-engine-edge.js
Normal file
File diff suppressed because one or more lines are too long
35
generated/prisma/runtime/wasm.js
Normal file
35
generated/prisma/runtime/wasm.js
Normal file
File diff suppressed because one or more lines are too long
48
generated/prisma/schema.prisma
Normal file
48
generated/prisma/schema.prisma
Normal 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
|
||||
}
|
||||
4
generated/prisma/wasm-edge-light-loader.mjs
Normal file
4
generated/prisma/wasm-edge-light-loader.mjs
Normal 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')
|
||||
4
generated/prisma/wasm-worker-loader.mjs
Normal file
4
generated/prisma/wasm-worker-loader.mjs
Normal 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
1
generated/prisma/wasm.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./default"
|
||||
231
generated/prisma/wasm.js
Normal file
231
generated/prisma/wasm.js
Normal 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
1530
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
package.json
Normal file
50
package.json
Normal 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
16
postcss.config.js
Normal 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
48
prisma/schema.prisma
Normal 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
30
prisma/seed.ts
Normal 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
17
src/App.tsx
Normal 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
64
src/AppRoutes.tsx
Normal 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
19
src/clientRoutes.ts
Normal 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;
|
||||
25
src/components/ProtectedRoute.tsx
Normal file
25
src/components/ProtectedRoute.tsx
Normal 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
26
src/frontend.tsx
Normal 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
187
src/index.css
Normal 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
13
src/index.html
Normal 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
46
src/index.tsx
Normal 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
11
src/lib/apiFetch.ts
Normal 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
1
src/logo.svg
Normal 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
7
src/pages/Home.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Home</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
84
src/pages/Login.tsx
Normal file
84
src/pages/Login.tsx
Normal 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
7
src/pages/NotFound.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<h1>404 Not Found</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
327
src/pages/sq/dashboard/apikey/apikey_page.tsx
Normal file
327
src/pages/sq/dashboard/apikey/apikey_page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
291
src/pages/sq/dashboard/dashboard_layout.tsx
Normal file
291
src/pages/sq/dashboard/dashboard_layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
src/pages/sq/dashboard/dashboard_page.tsx
Normal file
7
src/pages/sq/dashboard/dashboard_page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
src/pages/sq/dashboard/wajs/wajs_home.tsx
Normal file
3
src/pages/sq/dashboard/wajs/wajs_home.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function WajsHome() {
|
||||
return <h1>Wajs Home</h1>;
|
||||
}
|
||||
48
src/pages/sq/dashboard/wajs/wajs_layout.tsx
Normal file
48
src/pages/sq/dashboard/wajs/wajs_layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
316
src/pages/sq/dashboard/webhook/webhook_create.tsx
Normal file
316
src/pages/sq/dashboard/webhook/webhook_create.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
361
src/pages/sq/dashboard/webhook/webhook_edit.tsx
Normal file
361
src/pages/sq/dashboard/webhook/webhook_edit.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
249
src/pages/sq/dashboard/webhook/webhook_home.tsx
Normal file
249
src/pages/sq/dashboard/webhook/webhook_home.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
20
src/pages/sq/dashboard/webhook/webhook_layout.tsx
Normal file
20
src/pages/sq/dashboard/webhook/webhook_layout.tsx
Normal 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 />
|
||||
);
|
||||
}
|
||||
25
src/pages/sq/sq_layout.tsx
Normal file
25
src/pages/sq/sq_layout.tsx
Normal 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
22
src/pages/wajs/qrcode.tsx
Normal 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
8
src/react.svg
Normal 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 |
51
src/server/lib/get_value_by_path.ts
Normal file
51
src/server/lib/get_value_by_path.ts
Normal 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
11
src/server/lib/prisma.ts
Normal 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
|
||||
}
|
||||
343
src/server/lib/wa/wa_service.ts
Normal file
343
src/server/lib/wa/wa_service.ts
Normal 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.5–5 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();
|
||||
}
|
||||
54
src/server/middlewares/apiAuth.ts
Normal file
54
src/server/middlewares/apiAuth.ts
Normal 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' }
|
||||
}
|
||||
})
|
||||
}
|
||||
105
src/server/routes/apikey_route.ts
Normal file
105
src/server/routes/apikey_route.ts
Normal 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
|
||||
155
src/server/routes/auth_route.ts
Normal file
155
src/server/routes/auth_route.ts
Normal 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
|
||||
8
src/server/routes/darmasaba.ts
Normal file
8
src/server/routes/darmasaba.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Elysia from "elysia";
|
||||
|
||||
const Dashboard = new Elysia({
|
||||
prefix: "/dashboard"
|
||||
})
|
||||
.get("/apa", () => "Hello World")
|
||||
|
||||
export default Dashboard
|
||||
52
src/server/routes/wa_route.ts
Normal file
52
src/server/routes/wa_route.ts
Normal 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;
|
||||
147
src/server/routes/webhook_route.ts
Normal file
147
src/server/routes/webhook_route.ts
Normal 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
36
tsconfig.json
Normal 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
8
types/env.d.ts
vendored
Normal 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
73
x.ts
Normal 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
23
x.txt
Normal 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){
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user