diff --git a/.env.sample b/.env.sample index b5c1d3d..fc6f792 100644 --- a/.env.sample +++ b/.env.sample @@ -1 +1,8 @@ -NEXT_PUBLIC_MODE=default +# Config +PUBLIC_SERVER_URL=http://localhost:4000 +PUBLIC_MOCK_DATA=true +SERVER_PORT=4000 # optional, server port (alias: PORT, PUBLIC_SERVER_URL.port) (defaults to 4000 if none are specified) + +# Secrets +PUBLIC_X_ANON_KEY=... +X_API_KEY=... \ No newline at end of file diff --git a/.gitignore b/.gitignore index 89aa713..7954542 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,11 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +node_modules +/.env -node_modules/ - -.tool-versions -bun.lockb - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -.next -out - -# production -build - -# misc +# OS / Dev Env .DS_Store *.pem -.direnv - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel +/.direnv -# typescript +# Build Output +dist *.tsbuildinfo -next-env.d.ts diff --git a/bun.lock b/bun.lock index a469f5a..f382485 100644 --- a/bun.lock +++ b/bun.lock @@ -11,21 +11,41 @@ "name": "@packages/class_data", "version": "1.0.0", }, + "packages/models": { + "name": "@packages/models", + "dependencies": { + "elysia": "^1.3.5", + }, + "devDependencies": { + "@types/bun": "^1.2.18", + }, + "peerDependencies": { + "typescript": "^5.8.3", + }, + }, "packages/server": { "name": "@packages/server", + "dependencies": { + "@elysiajs/cors": "^1.3.3", + "@sinclair/typebox": "^0.34.37", + "elysia": "^1.3.5", + }, "devDependencies": { - "@types/bun": "latest", + "@types/bun": "^1.2.18", }, "peerDependencies": { - "typescript": "^5", + "typescript": "^5.8.3", }, }, "packages/web": { "name": "@packages/web", "version": "0.1.0", "dependencies": { + "@elysiajs/eden": "^1.3.2", "@headlessui/react": "^2.2.4", "@packages/class_data": "workspace:*", + "@packages/server": "workspace:server", + "@tanstack/react-query": "^5.83.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-hook-form": "^7.60.0", @@ -106,6 +126,10 @@ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-i2PKdn70kY++KEF/zkQFvQfX1e8SkA8hq4BgC+yE9dZqyLzB/XStY2MvwI3qswlRgnGpgncgqe0QYKVS1blksg=="], + "@elysiajs/cors": ["@elysiajs/cors@1.3.3", "", { "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-mYIU6PyMM6xIJuj7d27Vt0/wuzVKIEnFPjcvlkyd7t/m9xspAG37cwNjFxVOnyvY43oOd2I/oW2DB85utXpA2Q=="], + + "@elysiajs/eden": ["@elysiajs/eden@1.3.2", "", { "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-0bCU5DO7J7hQfS2y3O3399GtoxMWRDMgQNMTHOnf70/F2nF8SwGHvzwh3+wO62Ko5FMF7EYqTN9Csw/g/Q7qwg=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.6", "", { "os": "aix", "cpu": "ppc64" }, "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.6", "", { "os": "android", "cpu": "arm" }, "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg=="], @@ -182,6 +206,8 @@ "@packages/class_data": ["@packages/class_data@workspace:packages/class_data"], + "@packages/models": ["@packages/models@workspace:packages/models"], + "@packages/server": ["@packages/server@workspace:packages/server"], "@packages/web": ["@packages/web@workspace:packages/web"], @@ -242,6 +268,8 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.0", "", { "os": "win32", "cpu": "x64" }, "sha512-SRf1cytG7wqcHVLrBc9VtPK4pU5wxiB/lNIkNmW2ApKXIg+RpqwHfsaEK+e7eH4A1BpI6BX/aBWXxZCIrJg3uA=="], + "@sinclair/typebox": ["@sinclair/typebox@0.34.37", "", {}, "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw=="], + "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], "@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="], @@ -276,10 +304,18 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.11", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "postcss": "^8.4.41", "tailwindcss": "4.1.11" } }, "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA=="], + "@tanstack/query-core": ["@tanstack/query-core@5.83.0", "", {}, "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.83.0", "", { "dependencies": { "@tanstack/query-core": "5.83.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.12", "", { "dependencies": { "@tanstack/virtual-core": "3.13.12" }, "peerDependencies": { "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-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA=="], "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.12", "", {}, "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA=="], + "@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/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], @@ -324,20 +360,32 @@ "electron-to-chromium": ["electron-to-chromium@1.5.183", "", {}, "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA=="], + "elysia": ["elysia@1.3.5", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-XVIKXlKFwUT7Sta8GY+wO5reD9I0rqAEtaz1Z71UgJb61csYt8Q3W9al8rtL5RgumuRR8e3DNdzlUN9GkC4KDw=="], + "enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="], "esbuild": ["esbuild@0.25.6", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.6", "@esbuild/android-arm": "0.25.6", "@esbuild/android-arm64": "0.25.6", "@esbuild/android-x64": "0.25.6", "@esbuild/darwin-arm64": "0.25.6", "@esbuild/darwin-x64": "0.25.6", "@esbuild/freebsd-arm64": "0.25.6", "@esbuild/freebsd-x64": "0.25.6", "@esbuild/linux-arm": "0.25.6", "@esbuild/linux-arm64": "0.25.6", "@esbuild/linux-ia32": "0.25.6", "@esbuild/linux-loong64": "0.25.6", "@esbuild/linux-mips64el": "0.25.6", "@esbuild/linux-ppc64": "0.25.6", "@esbuild/linux-riscv64": "0.25.6", "@esbuild/linux-s390x": "0.25.6", "@esbuild/linux-x64": "0.25.6", "@esbuild/netbsd-arm64": "0.25.6", "@esbuild/netbsd-x64": "0.25.6", "@esbuild/openbsd-arm64": "0.25.6", "@esbuild/openbsd-x64": "0.25.6", "@esbuild/openharmony-arm64": "0.25.6", "@esbuild/sunos-x64": "0.25.6", "@esbuild/win32-arm64": "0.25.6", "@esbuild/win32-ia32": "0.25.6", "@esbuild/win32-x64": "0.25.6" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "exact-mirror": ["exact-mirror@0.1.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw=="], + + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], + "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=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], @@ -384,6 +432,8 @@ "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], @@ -414,6 +464,8 @@ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "strtok3": ["strtok3@10.3.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw=="], + "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], "tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="], @@ -424,10 +476,14 @@ "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], + "token-types": ["token-types@6.0.3", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-IKJ6EzuPPWtKtEIEPpIdXv9j5j2LGJEYk0CKY2efgKoYKLBiZdh6iQkLVBow/CB3phyWAWCyk+bZeaimJn6uRQ=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="], + "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], diff --git a/package.json b/package.json index 7475a53..d4c7fea 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "packages/*" ], "scripts": { - "dev": "cd packages/web && bun dev", - "dev:mock": "cd packages/web && bun dev:mock", + "dev": "bun --filter=@packages/{web,server} dev", + "dev:mock": "bun --filter=@packages/{web,server} dev:mock", "build": "cd packages/web && bun run build", "check": "bunx biome check .", "fix": "bunx biome check . --fix" diff --git a/packages/models/models.ts b/packages/models/models.ts new file mode 100644 index 0000000..3eb537b --- /dev/null +++ b/packages/models/models.ts @@ -0,0 +1,15 @@ +import { t } from "elysia"; + +export type CreateUser = typeof CreateUser.static; +export const CreateUser = t.Object({ + name: t.String({ + minLength: 1, + maxLength: 255, + }), +}); + +export type User = typeof User.static; +export const User = t.Object({ + id: t.String({ format: "uuid" }), + ...CreateUser.properties, +}); diff --git a/packages/models/package.json b/packages/models/package.json new file mode 100644 index 0000000..9858896 --- /dev/null +++ b/packages/models/package.json @@ -0,0 +1,17 @@ +{ + "name": "@packages/models", + "type": "module", + "private": true, + "exports": { + ".": "./models.ts" + }, + "devDependencies": { + "@types/bun": "^1.2.18" + }, + "peerDependencies": { + "typescript": "^5.8.3" + }, + "dependencies": { + "elysia": "^1.3.5" + } +} diff --git a/packages/models/tsconfig.json b/packages/models/tsconfig.json new file mode 100644 index 0000000..9536a0f --- /dev/null +++ b/packages/models/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.base.json" +} diff --git a/packages/server/README.md b/packages/server/README.md deleted file mode 100644 index 95b56f8..0000000 --- a/packages/server/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# server - -To install dependencies: - -```bash -bun install -``` - -To run: - -```bash -bun run index.ts -``` - -This project was created using `bun init` in bun v1.2.14. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/packages/server/app.ts b/packages/server/app.ts new file mode 100644 index 0000000..93e1025 --- /dev/null +++ b/packages/server/app.ts @@ -0,0 +1,11 @@ +import { cors } from "@elysiajs/cors"; +import { Elysia } from "elysia"; +import { usersRouter } from "./router/users.sample.ts"; + +export const app = new Elysia({ + prefix: "/api", +}) + .use(cors()) + .group("/users", (app) => app.use(usersRouter)); + +export type App = typeof app; diff --git a/packages/server/index.ts b/packages/server/index.ts deleted file mode 100644 index 2a5e4b8..0000000 --- a/packages/server/index.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello via Bun!"); diff --git a/packages/server/package.json b/packages/server/package.json index ac8dca8..031f208 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,12 +1,23 @@ { "name": "@packages/server", - "module": "index.ts", + "module": "serve.ts", "type": "module", "private": true, + "exports": { + ".": "./app.ts" + }, + "scripts": { + "dev": "bun --env-file=../../.env run --watch ./serve.ts" + }, "devDependencies": { - "@types/bun": "latest" + "@types/bun": "^1.2.18" }, "peerDependencies": { - "typescript": "^5" + "typescript": "^5.8.3" + }, + "dependencies": { + "@elysiajs/cors": "^1.3.3", + "@sinclair/typebox": "^0.34.37", + "elysia": "^1.3.5" } } diff --git a/packages/server/router/users.sample.ts b/packages/server/router/users.sample.ts new file mode 100644 index 0000000..9171708 --- /dev/null +++ b/packages/server/router/users.sample.ts @@ -0,0 +1,43 @@ +import { CreateUser, type User } from "@packages/models"; +import { Elysia, status, t } from "elysia"; + +const users: User[] = []; + +export const usersRouter = new Elysia() + .get("/", () => users) + .get("/id", ({ query }) => users.find((user) => user.id === query.id), { + query: t.Object({ + id: t.String({ + format: "uuid", + }), + }), + }) + .post( + "/", + ({ body }) => { + users.push({ + id: crypto.randomUUID(), + ...body, + }); + return body; + }, + { + body: CreateUser, + }, + ) + .delete( + "/", + ({ query }) => { + const index = users.findIndex((user) => user.id === query.id); + if (index === -1) return status(404, "User not found"); + users.splice(index, 1); + return query.id; + }, + { + query: t.Object({ + id: t.String({ + format: "uuid", + }), + }), + }, + ); diff --git a/packages/server/serve.ts b/packages/server/serve.ts new file mode 100644 index 0000000..943f8b6 --- /dev/null +++ b/packages/server/serve.ts @@ -0,0 +1,17 @@ +import { app } from "./app.ts"; + +const port = + process.env.SERVER_PORT ?? + process.env.PORT ?? + parsePublicServerURL() ?? + "4000"; + +app.listen(port, () => + console.log(`Server started at http://localhost:${port}`), +); + +function parsePublicServerURL() { + if (!process.env.PUBLIC_SERVER_URL) return undefined; + const url = new URL(process.env.PUBLIC_SERVER_URL); + return url.port; +} diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index bcc38f7..9be82aa 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,3 +1,17 @@ { - "extends": ["../../tsconfig.base.json"] + "extends": ["../../tsconfig.base.json"], + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true + } } diff --git a/packages/web/package.json b/packages/web/package.json index 1f003be..c13d1a2 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -5,19 +5,21 @@ "type": "module", "scripts": { "dev": "vite", - "dev:mock": "VITE_MODE=mock vite", "build": "tsc && vite build", "preview": "vite preview", "check": "tsc --noEmit" }, "dependencies": { + "@elysiajs/eden": "^1.3.2", "@headlessui/react": "^2.2.4", + "@packages/class_data": "workspace:*", + "@packages/server": "workspace:*", + "@tanstack/react-query": "^5.83.0", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-router-dom": "^7.1.1", "react-hook-form": "^7.60.0", "react-icons": "^5.5.0", - "@packages/class_data": "workspace:*" + "react-router-dom": "^7.1.1" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.11", diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 884f5c0..b654f08 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -1,18 +1,20 @@ +import { QueryClientProvider } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; - import { ThemeContext } from "@/app/context"; import type { RegisterType, ThemeType } from "@/app/type"; import { UserContext, type UserContextValue } from "@/app/UserContext"; import { User } from "@/app/utils/user"; import Footer from "./app/components/Footer/index.tsx"; import Header from "./app/components/Header/index.tsx"; +import { queryClient } from "./lib/tanstack/client.ts"; import AboutUs from "./pages/AboutUs.tsx"; import Disclaimer from "./pages/Disclaimer.tsx"; import Home from "./pages/Home.tsx"; import HowToUse from "./pages/HowToUse.tsx"; import NotFound from "./pages/NotFound.tsx"; import Notion from "./pages/Notion.tsx"; +import UserManagement from "./sample/UserManagement.tsx"; /** * App コンポーネントは、アプリケーション全体のレイアウトを定義します。 @@ -49,22 +51,27 @@ export default function App() { return ( - - - - - - } /> - } /> - } /> - } /> - } /> - } /> - - - - - + + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + + ); diff --git a/packages/web/src/app/utils/user.ts b/packages/web/src/app/utils/user.ts index 3fa78d2..6060680 100644 --- a/packages/web/src/app/utils/user.ts +++ b/packages/web/src/app/utils/user.ts @@ -1,14 +1,13 @@ import type { RegisterType } from "@/app/type"; import { SampleUser } from "@/app/utils/mock_data"; - -const MODE = import.meta.env.VITE_MODE; +import { env } from "../../lib/env.ts"; export class User { private user: RegisterType | undefined; constructor() { if (typeof window !== "undefined") { - if (MODE === "mock") { + if (env.mockData) { this.user = SampleUser; } else { const storedUser = localStorage.getItem("user"); @@ -36,7 +35,7 @@ export class User { setUser(newUser: RegisterType): void { this.user = newUser; - if (typeof window !== "undefined" && MODE !== "mock") { + if (typeof window !== "undefined" && !env.mockData) { localStorage.setItem("user", JSON.stringify(newUser)); } } diff --git a/packages/web/src/lib/api.ts b/packages/web/src/lib/api.ts new file mode 100644 index 0000000..ff0684a --- /dev/null +++ b/packages/web/src/lib/api.ts @@ -0,0 +1,5 @@ +import { edenTreaty } from "@elysiajs/eden"; +import type { App } from "@packages/server"; +import { env } from "./env.ts"; + +export const api = edenTreaty(env.serverURL).api; diff --git a/packages/web/src/lib/env.ts b/packages/web/src/lib/env.ts new file mode 100644 index 0000000..8649b6a --- /dev/null +++ b/packages/web/src/lib/env.ts @@ -0,0 +1,31 @@ +export const env = { + mockData: boolean(import.meta.env.PUBLIC_MOCK_DATA), + serverURL: string(import.meta.env.PUBLIC_SERVER_URL, { + name: "PUBLIC_SERVER_URL", + }), +}; + +function boolean(value: string | undefined): boolean { + return value === "true" || value === "1"; +} + +/** + * if value != null -> return value + * if defaultValue != null -> return defaultValue + * default -> throws Error + * @param value 値 + * @param param1 + * @returns 結果 + */ +function string( + value: string | undefined, + { name, defaultValue }: { name?: string; defaultValue?: string }, +): string { + if (value != null) { + return value; + } + if (defaultValue != null) { + return defaultValue; + } + throw new Error(`${name} is undefined`); +} diff --git a/packages/web/src/lib/tanstack/client.ts b/packages/web/src/lib/tanstack/client.ts new file mode 100644 index 0000000..6c7b9de --- /dev/null +++ b/packages/web/src/lib/tanstack/client.ts @@ -0,0 +1,3 @@ +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient(); diff --git a/packages/web/src/lib/tanstack/keys.ts b/packages/web/src/lib/tanstack/keys.ts new file mode 100644 index 0000000..dec2146 --- /dev/null +++ b/packages/web/src/lib/tanstack/keys.ts @@ -0,0 +1,6 @@ +export const keys = { + users_sample: { + _: ["users_sample"], + list: ["users_sample", "list"], + }, +} as const; diff --git a/packages/web/src/lib/tanstack/users.sample.ts b/packages/web/src/lib/tanstack/users.sample.ts new file mode 100644 index 0000000..aaab020 --- /dev/null +++ b/packages/web/src/lib/tanstack/users.sample.ts @@ -0,0 +1,47 @@ +import type { CreateUser } from "@packages/models"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { api } from "@/lib/api.ts"; +import { queryClient } from "./client.ts"; +import { keys } from "./keys.ts"; + +export const useUserListQuery = () => + useQuery({ + queryKey: keys.users_sample.list, + queryFn: async () => { + const { data, error } = await api.users.get(); + if (error) throw error; + return data; + }, + }); + +export const useUserCreateMutation = () => + useMutation({ + mutationFn: async (user: CreateUser) => { + const { data, error } = await api.users.post(user); + if (error) throw error; + return data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: keys.users_sample._, + }); + }, + }); + +export const useUserDeleteMutation = () => + useMutation({ + mutationFn: async (id: string) => { + const { data, error } = await api.users.delete({ + $query: { + id, + }, + }); + if (error) throw error; + return data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: keys.users_sample._, + }); + }, + }); diff --git a/packages/web/src/sample/UserManagement.tsx b/packages/web/src/sample/UserManagement.tsx new file mode 100644 index 0000000..a2b3c7b --- /dev/null +++ b/packages/web/src/sample/UserManagement.tsx @@ -0,0 +1,73 @@ +import { + useUserCreateMutation, + useUserDeleteMutation, + useUserListQuery, +} from "@/lib/tanstack/users.sample.ts"; + +export default function UserManagement() { + const { data, isLoading } = useUserListQuery(); + const createUser = useUserCreateMutation(); + const deleteUser = useUserDeleteMutation(); + + return ( + + User Management + {isLoading ? ( + Loading... + ) : ( + + {data?.map((user) => ( + + + {user.name} + + deleteUser.mutate(user.id)} + disabled={deleteUser.isPending} + > + Delete + + + ))} + + )} + { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + createUser.mutate( + { + name: formData.get("name") as string, + }, + { + onSuccess: () => { + const inputEl = document.getElementById( + "form-input-name", + ) as HTMLInputElement; + inputEl.value = ""; + }, + }, + ); + }} + > + + + Create + + + + ); +} diff --git a/packages/web/src/vite-env.d.ts b/packages/web/src/vite-env.d.ts index e61d533..fd39b0d 100644 --- a/packages/web/src/vite-env.d.ts +++ b/packages/web/src/vite-env.d.ts @@ -1,7 +1,8 @@ /// interface ImportMetaEnv { - readonly VITE_MODE: string; + readonly PUBLIC_SERVER_URL: string; + readonly PUBLIC_MOCK_DATA: string; } interface ImportMeta { diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index e5dce0f..8c1cbce 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -9,6 +9,8 @@ export default defineConfig({ "@": path.resolve(__dirname, "./src"), }, }, + envDir: "../../", + envPrefix: "PUBLIC_", server: { port: 3000, },
Loading...