tambahan
This commit is contained in:
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
/generated/prisma
|
||||||
21
README.md
Normal file
21
README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# bun-react-template
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
To run for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun start
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.2.21. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
||||||
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;
|
||||||
|
}
|
||||||
299
bun.lock
Normal file
299
bun.lock
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "bun-react-template",
|
||||||
|
"dependencies": {
|
||||||
|
"@elysiajs/cors": "^1.4.0",
|
||||||
|
"@elysiajs/eden": "^1.4.1",
|
||||||
|
"@elysiajs/swagger": "^1.3.1",
|
||||||
|
"@mantine/core": "^8.3.2",
|
||||||
|
"@mantine/hooks": "^8.3.2",
|
||||||
|
"@prisma/client": "^6.16.2",
|
||||||
|
"@tabler/icons-react": "^3.35.0",
|
||||||
|
"add": "^2.0.6",
|
||||||
|
"dedent": "^1.7.0",
|
||||||
|
"drizzle-orm": "^0.44.5",
|
||||||
|
"elysia": "^1.4.7",
|
||||||
|
"nanoid": "^5.1.6",
|
||||||
|
"postgres": "^3.4.7",
|
||||||
|
"prisma": "^6.16.2",
|
||||||
|
"react": "^19",
|
||||||
|
"react-dom": "^19",
|
||||||
|
"react-router-dom": "^7.9.1",
|
||||||
|
"valtio": "^2.1.7",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"postcss-preset-mantine": "^1.18.0",
|
||||||
|
"postcss-simple-vars": "^7.0.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.1", "", { "peerDependencies": { "elysia": ">= 1.4.0-exp.0" } }, "sha512-9VXMau/cvafuBa1r19ucKi+l9eesCmeuvD6uYSeq5MFO/URc233JaxZmUlWQ8gztu+pp6L7auTZdkzOQz26O+A=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
|
"@mantine/core": ["@mantine/core@8.3.2", "", { "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.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-uIHC9ooEZ9E+/pw8ag4f8pi0GmwSQ1DYnETjr4a4ZNVKJHfVv5NSkjprBxPrKJq9oox/SdcrAWy5XlKTwBzRag=="],
|
||||||
|
|
||||||
|
"@mantine/hooks": ["@mantine/hooks@8.3.2", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-urDgQJNAs2t2mAyGaA+7uNsBMRn9U/ccvi+ZUl5ef3/Wzfv5KYHe9LA9DBNhn24BTSewxrI27W0EFpFxv/Jsbg=="],
|
||||||
|
|
||||||
|
"@prisma/client": ["@prisma/client@6.16.2", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-E00PxBcalMfYO/TWnXobBVUai6eW/g5OsifWQsQDzJYm7yaY+IRLo7ZLsaefi0QkTpxfuhFcQ/w180i6kX3iJw=="],
|
||||||
|
|
||||||
|
"@prisma/config": ["@prisma/config@6.16.2", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.16.12", "empathic": "2.0.0" } }, "sha512-mKXSUrcqXj0LXWPmJsK2s3p9PN+aoAbyMx7m5E1v1FufofR1ZpPoIArjjzOIm+bJRLLvYftoNYLx1tbHgF9/yg=="],
|
||||||
|
|
||||||
|
"@prisma/debug": ["@prisma/debug@6.16.2", "", {}, "sha512-bo4/gA/HVV6u8YK2uY6glhNsJ7r+k/i5iQ9ny/3q5bt9ijCj7WMPUwfTKPvtEgLP+/r26Z686ly11hhcLiQ8zA=="],
|
||||||
|
|
||||||
|
"@prisma/engines": ["@prisma/engines@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/fetch-engine": "6.16.2", "@prisma/get-platform": "6.16.2" } }, "sha512-7yf3AjfPUgsg/l7JSu1iEhsmZZ/YE00yURPjTikqm2z4btM0bCl2coFtTGfeSOWbQMmq45Jab+53yGUIAT1sjA=="],
|
||||||
|
|
||||||
|
"@prisma/engines-version": ["@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "", {}, "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA=="],
|
||||||
|
|
||||||
|
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/get-platform": "6.16.2" } }, "sha512-wPnZ8DMRqpgzye758ZvfAMiNJRuYpz+rhgEBZi60ZqDIgOU2694oJxiuu3GKFeYeR/hXxso4/2oBC243t/whxQ=="],
|
||||||
|
|
||||||
|
"@prisma/get-platform": ["@prisma/get-platform@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2" } }, "sha512-U/P36Uke5wS7r1+omtAgJpEB94tlT4SdlgaeTc6HVTTT93pXj7zZ+B/cZnmnvjcNPfWddgoDx8RLjmQwqGDYyA=="],
|
||||||
|
|
||||||
|
"@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.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
||||||
|
|
||||||
|
"@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||||
|
|
||||||
|
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
|
||||||
|
|
||||||
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="],
|
||||||
|
|
||||||
|
"deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="],
|
||||||
|
|
||||||
|
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||||
|
|
||||||
|
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||||
|
|
||||||
|
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
||||||
|
|
||||||
|
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||||
|
|
||||||
|
"drizzle-orm": ["drizzle-orm@0.44.5", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ=="],
|
||||||
|
|
||||||
|
"effect": ["effect@3.16.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg=="],
|
||||||
|
|
||||||
|
"elysia": ["elysia@1.4.7", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "openapi-types": ">= 12.0.0" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-6dZjPO5wBBilZPzqZpuMq/bMiHn7jQ94sweiPTTR9MsofliZJcwcRB0XHAr7AvKblOlU72P+Cu8z6JNhZK8u/A=="],
|
||||||
|
|
||||||
|
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
|
|
||||||
|
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
||||||
|
"jiti": ["jiti@2.6.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
|
||||||
|
|
||||||
|
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||||
|
|
||||||
|
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||||
|
|
||||||
|
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
|
||||||
|
|
||||||
|
"prisma": ["prisma@6.16.2", "", { "dependencies": { "@prisma/config": "6.16.2", "@prisma/engines": "6.16.2" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA=="],
|
||||||
|
|
||||||
|
"proxy-compare": ["proxy-compare@3.0.1", "", {}, "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q=="],
|
||||||
|
|
||||||
|
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
|
||||||
|
|
||||||
|
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||||
|
|
||||||
|
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
|
||||||
|
|
||||||
|
"react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="],
|
||||||
|
|
||||||
|
"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-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.1", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g=="],
|
||||||
|
|
||||||
|
"react-router-dom": ["react-router-dom@7.9.1", "", { "dependencies": { "react-router": "7.9.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||||
|
|
||||||
|
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||||
|
|
||||||
|
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
|
"valtio": ["valtio@2.1.7", "", { "dependencies": { "proxy-compare": "^3.0.1" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-DwJhCDpujuQuKdJ2H84VbTjEJJteaSmqsuUltsfbfdbotVfNeTE4K/qc/Wi57I9x8/2ed4JNdjEna7O6PfavRg=="],
|
||||||
|
|
||||||
|
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"c12/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"nypm/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
|
"@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[serve.static]
|
||||||
|
env = "BUN_PUBLIC_*"
|
||||||
49
docker/compose.yml
Normal file
49
docker/compose.yml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
services:
|
||||||
|
sort-proxy:
|
||||||
|
image: tecnativa/docker-socket-proxy
|
||||||
|
container_name: sort-proxy
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
CONTAINERS: 1
|
||||||
|
POST: 1
|
||||||
|
PING: 1
|
||||||
|
networks:
|
||||||
|
- sort
|
||||||
|
sort-dev:
|
||||||
|
image: bip/dev:latest
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
context: .
|
||||||
|
target: dev
|
||||||
|
container_name: sort-dev
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./data/dev:/app
|
||||||
|
- ./data/ssh/authorized_keys:/home/bip/.ssh/authorized_keys:ro
|
||||||
|
networks:
|
||||||
|
- sort
|
||||||
|
sort-prod:
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
context: .
|
||||||
|
target: prod
|
||||||
|
image: bip/prod:latest
|
||||||
|
container_name: sort-prod
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./data/prod:/app
|
||||||
|
networks:
|
||||||
|
- sort
|
||||||
|
sort-frpc:
|
||||||
|
image: snowdreamtech/frpc:latest
|
||||||
|
container_name: sort-frpc
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./data/frpc/frpc.toml:/etc/frp/frpc.toml:ro
|
||||||
|
networks:
|
||||||
|
- sort
|
||||||
|
networks:
|
||||||
|
sort:
|
||||||
|
driver: bridge
|
||||||
40
docker/gen
Normal file
40
docker/gen
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Generating directory..."
|
||||||
|
mkdir -p data data/dev data/prod data/postgres data/frpc data/ssh
|
||||||
|
|
||||||
|
echo "Generating authorized_keys..."
|
||||||
|
touch data/ssh/authorized_keys
|
||||||
|
|
||||||
|
echo "Generating frpc.toml..."
|
||||||
|
touch data/frpc/frpc.toml
|
||||||
|
|
||||||
|
echo "Generating frpc.toml content..."
|
||||||
|
cat > data/frpc/frpc.toml <<EOF
|
||||||
|
[common]
|
||||||
|
server_addr = "85.31.224.xxx"
|
||||||
|
server_port = 7000
|
||||||
|
transport.tcp_mux = true
|
||||||
|
transport.pool_count = 5
|
||||||
|
transport.tls.enable = true
|
||||||
|
|
||||||
|
auth_token = ""
|
||||||
|
|
||||||
|
[ssh-cld-dkr-staging-hipmi.wibudev.com]
|
||||||
|
type = tcp
|
||||||
|
local_ip = hipmi-dev
|
||||||
|
local_port = 22
|
||||||
|
remote_port = 5102
|
||||||
|
|
||||||
|
[postgres-cld-dkr-staging-hipmi.wibudev.com]
|
||||||
|
type = tcp
|
||||||
|
local_ip = hipmi-dev
|
||||||
|
local_port = 5432
|
||||||
|
remote_port = 5202
|
||||||
|
|
||||||
|
[cld-dkr-staging-hipmi.wibudev.com]
|
||||||
|
type = http
|
||||||
|
local_ip = hipmi-prod
|
||||||
|
local_port = 3000
|
||||||
|
custom_domains = "cld-dkr-staging-hipmi.wibudev.com"
|
||||||
|
EOF
|
||||||
39
package.json
Normal file
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@elysiajs/cors": "^1.4.0",
|
||||||
|
"@elysiajs/eden": "^1.4.1",
|
||||||
|
"@elysiajs/swagger": "^1.3.1",
|
||||||
|
"@mantine/core": "^8.3.2",
|
||||||
|
"@mantine/hooks": "^8.3.2",
|
||||||
|
"@prisma/client": "^6.16.2",
|
||||||
|
"@tabler/icons-react": "^3.35.0",
|
||||||
|
"add": "^2.0.6",
|
||||||
|
"dedent": "^1.7.0",
|
||||||
|
"drizzle-orm": "^0.44.5",
|
||||||
|
"elysia": "^1.4.7",
|
||||||
|
"nanoid": "^5.1.6",
|
||||||
|
"postgres": "^3.4.7",
|
||||||
|
"prisma": "^6.16.2",
|
||||||
|
"react": "^19",
|
||||||
|
"react-dom": "^19",
|
||||||
|
"react-router-dom": "^7.9.1",
|
||||||
|
"valtio": "^2.1.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"postcss-preset-mantine": "^1.18.0",
|
||||||
|
"postcss-simple-vars": "^7.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
postcss.config.cjs
Normal file
14
postcss.config.cjs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
BIN
prisma/dev.db
Normal file
BIN
prisma/dev.db
Normal file
Binary file not shown.
17
prisma/schema.prisma
Normal file
17
prisma/schema.prisma
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
output = "../generated/prisma"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Path {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
from String @unique
|
||||||
|
to String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
18
src/App.tsx
Normal file
18
src/App.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import '@mantine/core/styles.css';
|
||||||
|
import { MantineProvider } from "@mantine/core";
|
||||||
|
import AppRoutes from "./AppRoutes";
|
||||||
|
import { loadPass } from './client/lib/state';
|
||||||
|
|
||||||
|
loadPass();
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
return (
|
||||||
|
<MantineProvider
|
||||||
|
defaultColorScheme="dark"
|
||||||
|
>
|
||||||
|
<AppRoutes />
|
||||||
|
</MantineProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
12
src/AppRoutes.tsx
Normal file
12
src/AppRoutes.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
|
import Home from "./client/pages/home";
|
||||||
|
|
||||||
|
export default function AppRoutes() {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
src/client/lib/apiFetch.ts
Normal file
11
src/client/lib/apiFetch.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { AppServer } from '@/index'
|
||||||
|
import { treaty } from '@elysiajs/eden'
|
||||||
|
|
||||||
|
const URL = process.env.BUN_PUBLIC_BASE_URL
|
||||||
|
if (!URL) {
|
||||||
|
throw new Error('BUN_PUBLIC_BASE_URL is not defined')
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiFetch = treaty<AppServer>(URL)
|
||||||
|
|
||||||
|
export default apiFetch
|
||||||
27
src/client/lib/state.ts
Normal file
27
src/client/lib/state.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { proxy } from "valtio";
|
||||||
|
|
||||||
|
export const state = proxy({
|
||||||
|
pass: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
export function loadPass() {
|
||||||
|
const pass = localStorage.getItem("pass");
|
||||||
|
if (pass && pass === "Makuro_123") {
|
||||||
|
state.pass = pass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function savePass(pass: string) {
|
||||||
|
localStorage.setItem("pass", pass);
|
||||||
|
state.pass = pass;
|
||||||
|
loadPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removePass() {
|
||||||
|
localStorage.removeItem("pass");
|
||||||
|
state.pass = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
144
src/client/pages/home.tsx
Normal file
144
src/client/pages/home.tsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { Anchor, Button, Container, CopyButton, Group, Stack, Table, TextInput } from "@mantine/core";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import { removePass, savePass, state } from "../lib/state";
|
||||||
|
interface Path {
|
||||||
|
id: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const [from, setFrom] = useState("");
|
||||||
|
const [to, setTo] = useState("");
|
||||||
|
const [paths, setPaths] = useState<Path[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [passInput, setPassInput] = useState("");
|
||||||
|
const { pass } = useSnapshot(state);
|
||||||
|
|
||||||
|
|
||||||
|
// fetch list path
|
||||||
|
const fetchPaths = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/path/list?page=1&limit=20");
|
||||||
|
const json = await res.json();
|
||||||
|
setPaths(json.data ?? []);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPaths();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// create path
|
||||||
|
const createPath = async () => {
|
||||||
|
const res = await fetch("/api/path/create", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ from, to }),
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.success) {
|
||||||
|
setFrom("");
|
||||||
|
setTo("");
|
||||||
|
fetchPaths();
|
||||||
|
} else {
|
||||||
|
alert(json.message || "Gagal membuat path");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// delete path
|
||||||
|
const deletePath = async (id: string) => {
|
||||||
|
await fetch(`/api/path/remove?id=${id}`, { method: "DELETE" });
|
||||||
|
fetchPaths();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pass !== "Makuro_123") {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<div style={{ padding: 20 }}>
|
||||||
|
<h1>Path Manager</h1>
|
||||||
|
<TextInput value={passInput} onChange={(event) => {
|
||||||
|
setPassInput(event.currentTarget.value);
|
||||||
|
}} onKeyDownCapture={(key) => {
|
||||||
|
if (passInput.length > 4 && key.key === "Enter") {
|
||||||
|
console.log("enter");
|
||||||
|
savePass(passInput);
|
||||||
|
setPassInput("");
|
||||||
|
}
|
||||||
|
}} />
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Stack>
|
||||||
|
<Group justify="end">
|
||||||
|
<Button variant="outline" color="red" onClick={removePass}>Logout</Button>
|
||||||
|
</Group>
|
||||||
|
<div style={{ padding: 20 }}>
|
||||||
|
<h1>Path Manager</h1>
|
||||||
|
|
||||||
|
<Group>
|
||||||
|
<TextInput
|
||||||
|
placeholder="from"
|
||||||
|
value={from}
|
||||||
|
onChange={(e) => setFrom(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="to"
|
||||||
|
value={to}
|
||||||
|
onChange={(e) => setTo(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<Button onClick={createPath}>Create</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Table striped highlightOnHover withTableBorder mt="md">
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>No</Table.Th>
|
||||||
|
<Table.Th>From</Table.Th>
|
||||||
|
<Table.Th>To</Table.Th>
|
||||||
|
<Table.Th>Action</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{paths.map((p, i) => (
|
||||||
|
<Table.Tr key={p.id}>
|
||||||
|
<Table.Td>{i + 1}</Table.Td>
|
||||||
|
<Table.Td>{p.from}</Table.Td>
|
||||||
|
<Table.Td >
|
||||||
|
<Anchor href={p.to} target="_blank">{p.to}</Anchor>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Group>
|
||||||
|
<CopyButton value={`${window.location.origin}/${p.from}`} >
|
||||||
|
{({ copied, copy }) => (
|
||||||
|
<Button size="xs" variant={copied ? "default" : "outline"} onClick={copy}>
|
||||||
|
{copied ? "Copied!" : "Copy"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</CopyButton>
|
||||||
|
<Button color="red" size="xs" onClick={() => deletePath(p.id)}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
{loading && <p>Loading...</p>}
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
6
src/clientRoutes.ts
Normal file
6
src/clientRoutes.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// AUTO-GENERATED FILE
|
||||||
|
const clientRoutes = {
|
||||||
|
"/": "/"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export default clientRoutes;
|
||||||
16
src/db/prisma.ts
Normal file
16
src/db/prisma.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { PrismaClient } from "generated/prisma";
|
||||||
|
|
||||||
|
|
||||||
|
// Gunakan globalThis untuk mencegah multiple instance saat hot reload
|
||||||
|
const globalForPrisma = globalThis as unknown as {
|
||||||
|
prisma: PrismaClient | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prisma =
|
||||||
|
globalForPrisma.prisma ??
|
||||||
|
new PrismaClient();
|
||||||
|
|
||||||
|
// Assign hanya di development, biar tidak leak di production
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
globalForPrisma.prisma = prisma;
|
||||||
|
}
|
||||||
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>
|
||||||
199
src/index.tsx
Normal file
199
src/index.tsx
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import { prisma } from "./db/prisma";
|
||||||
|
import index from "./index.html";
|
||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
import { swagger } from "@elysiajs/swagger";
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
const Swagger = new Elysia()
|
||||||
|
.use(swagger({
|
||||||
|
path: "/docs",
|
||||||
|
}))
|
||||||
|
|
||||||
|
const Api = new Elysia({
|
||||||
|
prefix: "/api",
|
||||||
|
})
|
||||||
|
.use(Swagger)
|
||||||
|
.post("/path/create", async ({ body, query }) => {
|
||||||
|
if (query?.force) {
|
||||||
|
const path = await prisma.path.upsert({
|
||||||
|
where: {
|
||||||
|
from: body.from,
|
||||||
|
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
from: body.from,
|
||||||
|
to: body.to,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
to: body.to,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectPath = await prisma.path.findUnique({
|
||||||
|
where: {
|
||||||
|
from: body.from,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectPath) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Path already exists",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = await prisma.path.create({
|
||||||
|
data: {
|
||||||
|
from: body.from,
|
||||||
|
to: body.to,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
}, {
|
||||||
|
query: t.Object({
|
||||||
|
force: t.Optional(t.Boolean()),
|
||||||
|
}),
|
||||||
|
body: t.Object({
|
||||||
|
from: t.String(),
|
||||||
|
to: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.get("/path/get", async ({ query }) => {
|
||||||
|
if (query.id) {
|
||||||
|
const path = await prisma.path.findUnique({
|
||||||
|
where: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.from) {
|
||||||
|
const path = await prisma.path.findUnique({
|
||||||
|
where: {
|
||||||
|
from: query.from,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
error: "path or from is required",
|
||||||
|
};
|
||||||
|
}, {
|
||||||
|
query: t.Object({
|
||||||
|
id: t.Optional(t.String()),
|
||||||
|
from: t.Optional(t.String())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.get("/path/list", async ({ query }) => {
|
||||||
|
const paths = await prisma.path.findMany({
|
||||||
|
skip: (Number(query.page) - 1) * Number(query.limit),
|
||||||
|
take: Number(query.limit),
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: paths,
|
||||||
|
};
|
||||||
|
}, {
|
||||||
|
query: t.Object({
|
||||||
|
page: t.Optional(t.String({ default: "1" })),
|
||||||
|
limit: t.Optional(t.String({ default: "10" })),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.delete("/path/remove", async ({ query }) => {
|
||||||
|
const path = await prisma.path.delete({
|
||||||
|
where: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
}, {
|
||||||
|
query: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.patch("/path/update", async ({ query }) => {
|
||||||
|
const path = await prisma.path.update({
|
||||||
|
where: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
to: query.to,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
}, {
|
||||||
|
query: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
to: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.put("/path/replace", async ({ query }) => {
|
||||||
|
const path = await prisma.path.update({
|
||||||
|
where: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
to: query.to,
|
||||||
|
from: query.from,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
}, {
|
||||||
|
query: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
to: t.String(),
|
||||||
|
from: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const ServerApp = new Elysia()
|
||||||
|
.get("/:path", async ({ params }) => {
|
||||||
|
const path = await prisma.path.findUnique({
|
||||||
|
where: {
|
||||||
|
from: params.path,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
// HTTP redirect ke tujuan
|
||||||
|
return Response.redirect(path.to, 302);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response("Path not found", { status: 404 });
|
||||||
|
}, {
|
||||||
|
params: t.Object({
|
||||||
|
path: t.String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.use(Api)
|
||||||
|
.get("*", index)
|
||||||
|
.listen(PORT, () => {
|
||||||
|
console.log(`Server running at http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AppServer = typeof ServerApp;
|
||||||
|
|
||||||
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 |
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 |
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"]
|
||||||
|
}
|
||||||
7
types/env.d.ts
vendored
Normal file
7
types/env.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
declare namespace NodeJS {
|
||||||
|
interface ProcessEnv {
|
||||||
|
DATABASE_URL?: string;
|
||||||
|
PORT?: string;
|
||||||
|
BUN_PUBLIC_BASE_URL?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
145
x.sh
Normal file
145
x.sh
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as os from "os";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
const CONFIG_FILE = path.join(os.homedir(), ".frpdev.conf");
|
||||||
|
|
||||||
|
interface FrpConfig {
|
||||||
|
FRP_HOST: string;
|
||||||
|
FRP_PORT: string;
|
||||||
|
FRP_USER: string;
|
||||||
|
FRP_SECRET: string;
|
||||||
|
FRP_PROTO: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buat file config default kosong jika belum ada
|
||||||
|
*/
|
||||||
|
function ensureConfigFile(): void {
|
||||||
|
if (!fs.existsSync(CONFIG_FILE)) {
|
||||||
|
const template = `FRP_HOST="frp.wibudev.com"
|
||||||
|
FRP_PORT="443"
|
||||||
|
FRP_USER="admin"
|
||||||
|
FRP_SECRET="admin123"
|
||||||
|
FRP_PROTO="https"
|
||||||
|
`;
|
||||||
|
fs.writeFileSync(CONFIG_FILE, template, { encoding: "utf8", mode: 0o600 });
|
||||||
|
console.log(`⚠️ Config not found. Created template at: ${CONFIG_FILE}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load config dari file .frpdev.conf
|
||||||
|
*/
|
||||||
|
function loadConfig(): FrpConfig {
|
||||||
|
ensureConfigFile();
|
||||||
|
|
||||||
|
const raw = fs.readFileSync(CONFIG_FILE, "utf8");
|
||||||
|
const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
|
||||||
|
|
||||||
|
const conf: Record<string, string> = {};
|
||||||
|
for (const line of lines) {
|
||||||
|
const [key, ...rest] = line.split("=");
|
||||||
|
if (!key) continue;
|
||||||
|
let value = rest.join("=").trim();
|
||||||
|
|
||||||
|
if (value.startsWith('"') && value.endsWith('"')) {
|
||||||
|
value = value.slice(1, -1);
|
||||||
|
}
|
||||||
|
conf[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
FRP_HOST: conf["FRP_HOST"] || "",
|
||||||
|
FRP_PORT: conf["FRP_PORT"] || "443",
|
||||||
|
FRP_USER: conf["FRP_USER"] || "",
|
||||||
|
FRP_SECRET: conf["FRP_SECRET"] || "",
|
||||||
|
FRP_PROTO: conf["FRP_PROTO"] || "https",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchFrp(config: FrpConfig, url: string): Promise<any> {
|
||||||
|
const fullUrl = `${config.FRP_PROTO}://${config.FRP_HOST}:${config.FRP_PORT}${url}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(fullUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization:
|
||||||
|
"Basic " +
|
||||||
|
Buffer.from(`${config.FRP_USER}:${config.FRP_SECRET}`).toString(
|
||||||
|
"base64"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resp.ok) {
|
||||||
|
return { proxies: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await resp.json();
|
||||||
|
return data;
|
||||||
|
} catch {
|
||||||
|
return { proxies: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format array of objects jadi table string
|
||||||
|
*/
|
||||||
|
function formatTable(headers: string[], rows: string[][]): string {
|
||||||
|
const allRows = [headers, ...rows];
|
||||||
|
const colWidths = headers.map((_, i) =>
|
||||||
|
Math.max(...allRows.map((row) => (row[i] || "").length))
|
||||||
|
);
|
||||||
|
|
||||||
|
return allRows
|
||||||
|
.map((row, rowIndex) =>
|
||||||
|
row
|
||||||
|
.map((cell, i) => cell.padEnd(colWidths[i]))
|
||||||
|
.join(" ")
|
||||||
|
.trimEnd()
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
const API_TCP = "/api/proxy/tcp";
|
||||||
|
const API_HTTP = "/api/proxy/http";
|
||||||
|
|
||||||
|
const tcpResp = await fetchFrp(config, API_TCP);
|
||||||
|
const httpResp = await fetchFrp(config, API_HTTP);
|
||||||
|
|
||||||
|
// ==================== TCP ====================
|
||||||
|
console.log("========== TCP PROXIES ==========");
|
||||||
|
const tcpHeaders = ["NAME", "STATUS", "TYPE", "PORT"];
|
||||||
|
const tcpRows: string[][] = (tcpResp.proxies || []).map((p: any) => [
|
||||||
|
p.name ?? "-",
|
||||||
|
p.status ?? "-",
|
||||||
|
p.conf?.type === "unknown" ? "" : p.conf?.type ?? "",
|
||||||
|
p.conf?.remotePort?.toString() ?? "-",
|
||||||
|
]);
|
||||||
|
console.log(formatTable(tcpHeaders, tcpRows));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// ==================== HTTP ====================
|
||||||
|
console.log("========== HTTP PROXIES ==========");
|
||||||
|
const httpHeaders = ["NAME", "STATUS", "TYPE", "SUBDOMAIN", "CUSTOM_DOMAIN"];
|
||||||
|
const httpRows: string[][] = (httpResp.proxies || []).map((p: any) => [
|
||||||
|
p.name ?? "-",
|
||||||
|
p.status ?? "-",
|
||||||
|
p.conf?.type === "unknown" ? "" : p.conf?.type ?? "",
|
||||||
|
p.conf?.subdomain ?? "",
|
||||||
|
Array.isArray(p.conf?.customDomains)
|
||||||
|
? p.conf.customDomains.join(",")
|
||||||
|
: "",
|
||||||
|
]);
|
||||||
|
console.log(formatTable(httpHeaders, httpRows));
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("❌ Error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
157
x.ts
Normal file
157
x.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import * as os from "os";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
const CONFIG_FILE = path.join(os.homedir(), ".frpdev.conf");
|
||||||
|
|
||||||
|
interface FrpConfig {
|
||||||
|
FRP_HOST: string;
|
||||||
|
FRP_PORT: string;
|
||||||
|
FRP_USER: string;
|
||||||
|
FRP_SECRET: string;
|
||||||
|
FRP_PROTO: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProxyConf {
|
||||||
|
type?: string;
|
||||||
|
remotePort?: number;
|
||||||
|
subdomain?: string;
|
||||||
|
customDomains?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Proxy {
|
||||||
|
name?: string;
|
||||||
|
status?: string;
|
||||||
|
conf?: ProxyConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProxyResponse {
|
||||||
|
proxies?: Proxy[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pastikan config file ada. Jika tidak ada -> stop proses.
|
||||||
|
*/
|
||||||
|
async function ensureConfigFile(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await fs.access(CONFIG_FILE);
|
||||||
|
} catch {
|
||||||
|
const template = `FRP_HOST=""
|
||||||
|
FRP_PORT="443"
|
||||||
|
FRP_USER=""
|
||||||
|
FRP_SECRET=""
|
||||||
|
FRP_PROTO="https"
|
||||||
|
`;
|
||||||
|
console.error(`❌ Config not found. Template created at: ${CONFIG_FILE}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load config dari file .frpdev.conf
|
||||||
|
*/
|
||||||
|
async function loadConfig(): Promise<FrpConfig> {
|
||||||
|
await ensureConfigFile();
|
||||||
|
|
||||||
|
const raw = await fs.readFile(CONFIG_FILE, "utf8");
|
||||||
|
const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
|
||||||
|
|
||||||
|
const conf: Record<string, string> = {};
|
||||||
|
for (const line of lines) {
|
||||||
|
const [key, ...rest] = line.split("=");
|
||||||
|
if (!key) continue;
|
||||||
|
let value = rest.join("=").trim();
|
||||||
|
|
||||||
|
if (value.startsWith('"') && value.endsWith('"')) {
|
||||||
|
value = value.slice(1, -1);
|
||||||
|
}
|
||||||
|
conf[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
FRP_HOST: conf["FRP_HOST"] || "",
|
||||||
|
FRP_PORT: conf["FRP_PORT"] || "443",
|
||||||
|
FRP_USER: conf["FRP_USER"] || "",
|
||||||
|
FRP_SECRET: conf["FRP_SECRET"] || "",
|
||||||
|
FRP_PROTO: conf["FRP_PROTO"] || "https",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchFrp(config: FrpConfig, url: string): Promise<ProxyResponse> {
|
||||||
|
const fullUrl = `${config.FRP_PROTO}://${config.FRP_HOST}:${config.FRP_PORT}${url}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(fullUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization:
|
||||||
|
"Basic " +
|
||||||
|
Buffer.from(`${config.FRP_USER}:${config.FRP_SECRET}`).toString("base64"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resp.ok) {
|
||||||
|
return { proxies: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: ProxyResponse = await resp.json();
|
||||||
|
return data;
|
||||||
|
} catch {
|
||||||
|
return { proxies: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format array of objects jadi table string
|
||||||
|
*/
|
||||||
|
function formatTable(headers: string[], rows: string[][]): string {
|
||||||
|
const allRows = [headers, ...rows];
|
||||||
|
const colWidths = headers.map((_, i) =>
|
||||||
|
Math.max(...allRows.map((row) => (row[i] || "").length))
|
||||||
|
);
|
||||||
|
|
||||||
|
return allRows
|
||||||
|
.map((row) =>
|
||||||
|
row.map((cell, i) => (cell || "").padEnd(colWidths[i] ?? 0)).join(" ").trimEnd()
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
const config = await loadConfig();
|
||||||
|
|
||||||
|
const API_TCP = "/api/proxy/tcp";
|
||||||
|
const API_HTTP = "/api/proxy/http";
|
||||||
|
|
||||||
|
const tcpResp = await fetchFrp(config, API_TCP);
|
||||||
|
const httpResp = await fetchFrp(config, API_HTTP);
|
||||||
|
|
||||||
|
// ==================== TCP ====================
|
||||||
|
console.log("========== TCP PROXIES ==========");
|
||||||
|
const tcpHeaders = ["NAME", "STATUS", "TYPE", "PORT"];
|
||||||
|
const tcpRows: string[][] = (tcpResp.proxies || []).map((p) => [
|
||||||
|
p.name ?? "-",
|
||||||
|
p.status ?? "-",
|
||||||
|
p.conf?.type === "unknown" ? "" : p.conf?.type ?? "",
|
||||||
|
p.conf?.remotePort?.toString() ?? "-",
|
||||||
|
]);
|
||||||
|
console.log(formatTable(tcpHeaders, tcpRows));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// ==================== HTTP ====================
|
||||||
|
console.log("========== HTTP PROXIES ==========");
|
||||||
|
const httpHeaders = ["NAME", "STATUS", "TYPE", "SUBDOMAIN", "CUSTOM_DOMAIN"];
|
||||||
|
const httpRows: string[][] = (httpResp.proxies || []).map((p) => [
|
||||||
|
p.name ?? "-",
|
||||||
|
p.status ?? "-",
|
||||||
|
p.conf?.type === "unknown" ? "" : p.conf?.type ?? "",
|
||||||
|
p.conf?.subdomain ?? "",
|
||||||
|
Array.isArray(p.conf?.customDomains) ? p.conf.customDomains.join(",") : "",
|
||||||
|
]);
|
||||||
|
console.log(formatTable(httpHeaders, httpRows));
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("❌ Error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user