Commit
·
ee7d090
0
Parent(s):
first commit
Browse files- .dockerignore +2 -0
- .env.example +2 -0
- .eslintignore +6 -0
- .eslintrc.json +21 -0
- .github/workflows/hugging-face.yml +19 -0
- .gitignore +7 -0
- .prettierignore +6 -0
- .prettierrc.json +6 -0
- Dockerfile +28 -0
- LICENSE +21 -0
- README.md +22 -0
- index.html +13 -0
- nodemon.json +5 -0
- package-lock.json +0 -0
- package.json +65 -0
- postcss.config.js +6 -0
- src/app/App.tsx +54 -0
- src/app/favicon.svg +1 -0
- src/app/globals.css +16 -0
- src/app/main.tsx +20 -0
- src/lib/helper.ts +58 -0
- src/server.ts +41 -0
- tailwind.config.js +8 -0
- tsconfig.json +24 -0
- tsconfig.server.json +11 -0
- vite.config.js +23 -0
.dockerignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules
|
| 2 |
+
npm-debug.log
|
.env.example
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
PORT=7860
|
| 2 |
+
VITE_SERVER_URL=http://localhost:7860/
|
.eslintignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules
|
| 2 |
+
.DS_Store
|
| 3 |
+
dist
|
| 4 |
+
dist-ssr
|
| 5 |
+
*.local
|
| 6 |
+
.eslintcache
|
.eslintrc.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"env": {
|
| 3 |
+
"browser": true,
|
| 4 |
+
"es2021": true,
|
| 5 |
+
"node": true
|
| 6 |
+
},
|
| 7 |
+
"extends": [
|
| 8 |
+
"eslint:recommended",
|
| 9 |
+
"plugin:@typescript-eslint/recommended",
|
| 10 |
+
"prettier"
|
| 11 |
+
],
|
| 12 |
+
"parser": "@typescript-eslint/parser",
|
| 13 |
+
"parserOptions": {
|
| 14 |
+
"ecmaVersion": 12,
|
| 15 |
+
"sourceType": "module"
|
| 16 |
+
},
|
| 17 |
+
"plugins": ["@typescript-eslint"],
|
| 18 |
+
"rules": {
|
| 19 |
+
"@typescript-eslint/no-explicit-any": "off"
|
| 20 |
+
}
|
| 21 |
+
}
|
.github/workflows/hugging-face.yml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Sync to Hugging Face hub
|
| 2 |
+
on:
|
| 3 |
+
push:
|
| 4 |
+
branches: [main]
|
| 5 |
+
|
| 6 |
+
# to run this workflow manually from the Actions tab
|
| 7 |
+
workflow_dispatch:
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
sync-to-hub:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
steps:
|
| 13 |
+
- uses: actions/checkout@v3
|
| 14 |
+
with:
|
| 15 |
+
fetch-depth: 0
|
| 16 |
+
- name: Push to hub
|
| 17 |
+
env:
|
| 18 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 19 |
+
run: git push https://lysine:$HF_TOKEN@huggingface.co/spaces/lysine/med main
|
.gitignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules
|
| 2 |
+
.DS_Store
|
| 3 |
+
dist
|
| 4 |
+
dist-ssr
|
| 5 |
+
*.local
|
| 6 |
+
.eslintcache
|
| 7 |
+
.env
|
.prettierignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules
|
| 2 |
+
.DS_Store
|
| 3 |
+
dist
|
| 4 |
+
dist-ssr
|
| 5 |
+
*.local
|
| 6 |
+
.eslintcache
|
.prettierrc.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"singleQuote": true,
|
| 3 |
+
"arrowParens": "avoid",
|
| 4 |
+
"tabWidth": 2,
|
| 5 |
+
"trailingComma": "es5"
|
| 6 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ARG VITE_SERVER_URL
|
| 2 |
+
|
| 3 |
+
FROM node:18
|
| 4 |
+
|
| 5 |
+
# Set up a new user named "user" with user ID 1000
|
| 6 |
+
RUN useradd -o -u 1000 user
|
| 7 |
+
|
| 8 |
+
# Switch to the "user" user
|
| 9 |
+
USER user
|
| 10 |
+
|
| 11 |
+
# Set home to the user's home directory
|
| 12 |
+
ENV HOME=/home/user \
|
| 13 |
+
PATH=/home/user/.local/bin:$PATH
|
| 14 |
+
|
| 15 |
+
# Set the working directory to the user's home directory
|
| 16 |
+
WORKDIR $HOME/app
|
| 17 |
+
|
| 18 |
+
# Copy the current directory contents into the container at $HOME/app setting the owner to the user
|
| 19 |
+
COPY --chown=user . $HOME/app
|
| 20 |
+
|
| 21 |
+
# Install npm dependencies
|
| 22 |
+
RUN npm install
|
| 23 |
+
|
| 24 |
+
# Build client and server
|
| 25 |
+
RUN export VITE_SERVER_URL=$MODEL_REPO_NAME && npm run build
|
| 26 |
+
|
| 27 |
+
EXPOSE 7860
|
| 28 |
+
CMD [ "npm", "run", "start" ]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Henry Lin
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: med
|
| 3 |
+
emoji: ➕
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: true
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# med
|
| 11 |
+
|
| 12 |
+
A central hub for all the clinical database apps.
|
| 13 |
+
|
| 14 |
+
[](https://lysine-med.hf.space/)
|
| 15 |
+
|
| 16 |
+
## License
|
| 17 |
+
|
| 18 |
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
| 19 |
+
|
| 20 |
+
## Acknowledgments
|
| 21 |
+
|
| 22 |
+
* Thanks [Hugging Face](https://huggingface.co/) for hosting the app online for free.
|
index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/src/app/favicon.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>Clinical Database</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="root"></div>
|
| 11 |
+
<script type="module" src="/src/app/main.tsx"></script>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
nodemon.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"watch": ["src"],
|
| 3 |
+
"ext": "js,jsx,ts,tsx,json",
|
| 4 |
+
"exec": "ts-node --esm --project tsconfig.server.json --experimental-specifier-resolution=node ./src/server"
|
| 5 |
+
}
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "med",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"description": "A central hub linking to all clinical databases.",
|
| 5 |
+
"private": true,
|
| 6 |
+
"type": "module",
|
| 7 |
+
"scripts": {
|
| 8 |
+
"dev": "concurrently \"npm run server:dev\" \"npm run client:dev\"",
|
| 9 |
+
"client:dev": "vite",
|
| 10 |
+
"server:dev": "nodemon",
|
| 11 |
+
"server:build": "tsc --project tsconfig.server.json",
|
| 12 |
+
"client:build": "vite build",
|
| 13 |
+
"build": "npm run server:build && npm run client:build",
|
| 14 |
+
"serve": "vite preview",
|
| 15 |
+
"test": "tsc && prettier --check . && eslint . && stylelint \"**/*.css\"",
|
| 16 |
+
"start": "node --experimental-specifier-resolution=node dist/server.js",
|
| 17 |
+
"docker": "npm run docker:build && npm run docker:run",
|
| 18 |
+
"docker:build": "docker build -t auscultate .",
|
| 19 |
+
"docker:run": "docker run -it -p 7860:7860 auscultate",
|
| 20 |
+
"lint": "eslint **/*.{js,jsx,ts,tsx} --fix --cache",
|
| 21 |
+
"format": "prettier **/*.{js,jsx,ts,tsx,css,html,json,md,mdx} --write"
|
| 22 |
+
},
|
| 23 |
+
"devDependencies": {
|
| 24 |
+
"@babel/core": "^7.22.10",
|
| 25 |
+
"@babel/node": "^7.22.10",
|
| 26 |
+
"@babel/preset-env": "^7.22.10",
|
| 27 |
+
"@types/cors": "^2.8.13",
|
| 28 |
+
"@types/express": "^4.17.17",
|
| 29 |
+
"@types/node": "^20.4.9",
|
| 30 |
+
"@types/react": "^18.2.20",
|
| 31 |
+
"@types/react-dom": "^18.2.7",
|
| 32 |
+
"@types/react-helmet": "^6.1.6",
|
| 33 |
+
"@types/react-router-dom": "^5.3.3",
|
| 34 |
+
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
| 35 |
+
"@typescript-eslint/parser": "^6.3.0",
|
| 36 |
+
"@vitejs/plugin-react-refresh": "^1.3.6",
|
| 37 |
+
"autoprefixer": "^10.4.14",
|
| 38 |
+
"concurrently": "^8.2.0",
|
| 39 |
+
"daisyui": "^3.5.1",
|
| 40 |
+
"eslint": "^8.46.0",
|
| 41 |
+
"eslint-config-prettier": "^9.0.0",
|
| 42 |
+
"http-proxy-middleware": "^2.0.6",
|
| 43 |
+
"nodemon": "^3.0.1",
|
| 44 |
+
"postcss": "^8.4.27",
|
| 45 |
+
"prettier": "3.0.1",
|
| 46 |
+
"tailwindcss": "^3.3.3",
|
| 47 |
+
"ts-node": "^10.9.1",
|
| 48 |
+
"typescript": "^5.1.6",
|
| 49 |
+
"vite": "^4.4.9"
|
| 50 |
+
},
|
| 51 |
+
"dependencies": {
|
| 52 |
+
"@hapi/boom": "^10.0.1",
|
| 53 |
+
"@vanillaes/csv": "^3.0.1",
|
| 54 |
+
"axios": "^1.4.0",
|
| 55 |
+
"cors": "^2.8.5",
|
| 56 |
+
"dotenv": "^16.3.1",
|
| 57 |
+
"express": "^4.18.2",
|
| 58 |
+
"react": "^18.2.0",
|
| 59 |
+
"react-dom": "^18.2.0",
|
| 60 |
+
"react-helmet": "^6.1.0",
|
| 61 |
+
"react-router-dom": "^6.15.0",
|
| 62 |
+
"wavesurfer.js": "^7.1.2",
|
| 63 |
+
"zod": "^3.21.4"
|
| 64 |
+
}
|
| 65 |
+
}
|
postcss.config.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
};
|
src/app/App.tsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Helmet } from 'react-helmet';
|
| 3 |
+
import { useNavigate } from 'react-router-dom';
|
| 4 |
+
|
| 5 |
+
const links = [
|
| 6 |
+
{
|
| 7 |
+
icon: (
|
| 8 |
+
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none">
|
| 9 |
+
<path
|
| 10 |
+
opacity="0.5"
|
| 11 |
+
d="M2.34594 11.2501C2.12458 10.5866 2 9.92019 2 9.26044C2 3.3495 7.50016 0.662637 12 5.49877C16.4998 0.662637 22 3.34931 22 9.2604C22 9.92017 21.8754 10.5866 21.6541 11.2501H18.6361L18.5241 11.25C17.9784 11.2491 17.4937 11.2483 17.0527 11.4447C16.6116 11.6411 16.2879 12.002 15.9233 12.4084L15.8485 12.4918L14.8192 13.6354C14.7164 13.7496 14.5379 13.7463 14.4401 13.6277L10.8889 9.32318C10.7493 9.15391 10.6 8.97281 10.454 8.8384C10.2839 8.68188 10.0325 8.50581 9.68096 8.4847C9.32945 8.46359 9.05875 8.60829 8.87115 8.74333C8.71006 8.8593 8.54016 9.02123 8.38136 9.17258L6.85172 10.6294C6.37995 11.0787 6.28151 11.1553 6.17854 11.1964C6.07557 11.2376 5.9515 11.2501 5.3 11.2501H2.34594Z"
|
| 12 |
+
fill="#1C274C"
|
| 13 |
+
/>
|
| 14 |
+
<path
|
| 15 |
+
d="M21.6538 11.2499H18.6359L18.5239 11.2497C17.9781 11.2489 17.4935 11.2481 17.0524 11.4445C16.6114 11.6409 16.2876 12.0018 15.9231 12.4082L15.8482 12.4915L14.819 13.6352C14.7162 13.7494 14.5377 13.7461 14.4399 13.6275L10.8886 9.32297C10.7491 9.1537 10.5998 8.9726 10.4537 8.83819C10.2837 8.68166 10.0322 8.5056 9.68072 8.48449C9.32922 8.46337 9.05852 8.60808 8.87092 8.74312C8.70983 8.85908 8.53993 9.02101 8.38113 9.17236L6.85149 10.6292C6.37972 11.0785 6.28128 11.155 6.17831 11.1962C6.07534 11.2374 5.95126 11.2499 5.29977 11.2499H2.3457C3.38166 14.3548 6.5372 17.3936 8.9615 19.3705C10.2935 20.4567 10.9595 20.9998 11.9998 20.9998C13.0401 20.9998 13.7061 20.4567 15.038 19.3705C17.4623 17.3936 20.6179 14.3548 21.6538 11.2499Z"
|
| 16 |
+
fill="#1C274C"
|
| 17 |
+
/>
|
| 18 |
+
</svg>
|
| 19 |
+
),
|
| 20 |
+
title: 'Auscultation',
|
| 21 |
+
description: 'Heart and breath sounds',
|
| 22 |
+
link: 'https://lysine-auscultate.hf.space/',
|
| 23 |
+
},
|
| 24 |
+
];
|
| 25 |
+
|
| 26 |
+
export default function App() {
|
| 27 |
+
const navigate = useNavigate();
|
| 28 |
+
return (
|
| 29 |
+
<div className="flex flex-col gap-8 w-full items-center p-4 pt-16">
|
| 30 |
+
<Helmet>
|
| 31 |
+
<title>Clinical Database</title>
|
| 32 |
+
</Helmet>
|
| 33 |
+
<p className="text-3xl text-center">Clinical Database</p>
|
| 34 |
+
<p>A series of websites presenting med-related data for practice.</p>
|
| 35 |
+
{links.map(link => (
|
| 36 |
+
<div className="card lg:card-side bg-base-300 shadow-xl lg:min-w-[500px]">
|
| 37 |
+
<figure className="bg-base-content p-4">{link.icon}</figure>
|
| 38 |
+
<div className="card-body">
|
| 39 |
+
<h2 className="card-title">{link.title}</h2>
|
| 40 |
+
<p>{link.description}</p>
|
| 41 |
+
<div className="card-actions justify-end mt-4">
|
| 42 |
+
<button
|
| 43 |
+
className="btn btn-primary"
|
| 44 |
+
onClick={() => navigate(link.link)}
|
| 45 |
+
>
|
| 46 |
+
Enter
|
| 47 |
+
</button>
|
| 48 |
+
</div>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
))}
|
| 52 |
+
</div>
|
| 53 |
+
);
|
| 54 |
+
}
|
src/app/favicon.svg
ADDED
|
|
src/app/globals.css
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
| 4 |
+
|
| 5 |
+
body {
|
| 6 |
+
margin: 0;
|
| 7 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
| 8 |
+
Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
| 9 |
+
-webkit-font-smoothing: antialiased;
|
| 10 |
+
-moz-osx-font-smoothing: grayscale;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
code {
|
| 14 |
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
| 15 |
+
monospace;
|
| 16 |
+
}
|
src/app/main.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { createRoot } from 'react-dom/client';
|
| 3 |
+
import './globals.css';
|
| 4 |
+
import App from './App';
|
| 5 |
+
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
| 6 |
+
|
| 7 |
+
const router = createBrowserRouter([
|
| 8 |
+
{
|
| 9 |
+
path: '/',
|
| 10 |
+
element: <App />,
|
| 11 |
+
},
|
| 12 |
+
]);
|
| 13 |
+
|
| 14 |
+
const container = document.querySelector('#root');
|
| 15 |
+
const root = createRoot(container!);
|
| 16 |
+
root.render(
|
| 17 |
+
<React.StrictMode>
|
| 18 |
+
<RouterProvider router={router} />
|
| 19 |
+
</React.StrictMode>
|
| 20 |
+
);
|
src/lib/helper.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Request, Response, NextFunction } from 'express';
|
| 2 |
+
import { AnyZodObject, z } from 'zod';
|
| 3 |
+
import { badRequest } from '@hapi/boom';
|
| 4 |
+
|
| 5 |
+
function indent(str: string, spaces: number) {
|
| 6 |
+
return str
|
| 7 |
+
.split('\n')
|
| 8 |
+
.map(line => ' '.repeat(spaces) + line)
|
| 9 |
+
.join('\n');
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
function extractZodMessage(error: any): string {
|
| 13 |
+
if (Array.isArray(error)) {
|
| 14 |
+
return error.map(extractZodMessage).join('\n');
|
| 15 |
+
} else {
|
| 16 |
+
let union: string[] = [];
|
| 17 |
+
if ('unionErrors' in error) {
|
| 18 |
+
union = error.unionErrors.map(extractZodMessage);
|
| 19 |
+
} else if ('issues' in error) {
|
| 20 |
+
union = error.issues.map(extractZodMessage);
|
| 21 |
+
}
|
| 22 |
+
if (
|
| 23 |
+
'message' in error &&
|
| 24 |
+
typeof error.message === 'string' &&
|
| 25 |
+
!error.message.includes('\n')
|
| 26 |
+
) {
|
| 27 |
+
if (union.length === 0) return error.message;
|
| 28 |
+
return error.message + '\n' + indent(union.join('\n'), 2);
|
| 29 |
+
} else if (union.length > 0) {
|
| 30 |
+
return union.join('\n');
|
| 31 |
+
} else {
|
| 32 |
+
return '';
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
export async function validate<T extends AnyZodObject>(
|
| 38 |
+
req: Request,
|
| 39 |
+
schema: T
|
| 40 |
+
): Promise<z.infer<T>> {
|
| 41 |
+
try {
|
| 42 |
+
return await schema.parseAsync(req);
|
| 43 |
+
} catch (error: any) {
|
| 44 |
+
throw badRequest(extractZodMessage(error));
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
export function wrap(
|
| 49 |
+
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>
|
| 50 |
+
) {
|
| 51 |
+
return async function (req: Request, res: Response, next: NextFunction) {
|
| 52 |
+
try {
|
| 53 |
+
return await fn(req, res, next);
|
| 54 |
+
} catch (err) {
|
| 55 |
+
next(err);
|
| 56 |
+
}
|
| 57 |
+
};
|
| 58 |
+
}
|
src/server.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import 'dotenv/config';
|
| 2 |
+
import express, { Request, Response, NextFunction } from 'express';
|
| 3 |
+
import cors from 'cors';
|
| 4 |
+
import { isBoom } from '@hapi/boom';
|
| 5 |
+
import { fileURLToPath } from 'url';
|
| 6 |
+
import { dirname, join } from 'path';
|
| 7 |
+
|
| 8 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 9 |
+
const __dirname = dirname(__filename);
|
| 10 |
+
|
| 11 |
+
const { PORT = 7860 } = process.env;
|
| 12 |
+
|
| 13 |
+
const app = express();
|
| 14 |
+
|
| 15 |
+
// Enable cross-origin resource sharing
|
| 16 |
+
app.use(cors());
|
| 17 |
+
|
| 18 |
+
// Middleware that parses json and looks at requests where the Content-Type header matches the type option.
|
| 19 |
+
app.use(express.json());
|
| 20 |
+
|
| 21 |
+
// Serve app production bundle
|
| 22 |
+
app.use(express.static('dist/app'));
|
| 23 |
+
|
| 24 |
+
app.use((err: unknown, _req: Request, res: Response, next: NextFunction) => {
|
| 25 |
+
if (res.headersSent) {
|
| 26 |
+
return next(err);
|
| 27 |
+
}
|
| 28 |
+
if (isBoom(err)) {
|
| 29 |
+
return res.status(err.output.statusCode).json(err.output.payload);
|
| 30 |
+
}
|
| 31 |
+
next(err);
|
| 32 |
+
});
|
| 33 |
+
|
| 34 |
+
// Handle client routing, return all requests to the app
|
| 35 |
+
app.get('*', (_req, res) => {
|
| 36 |
+
res.sendFile(join(__dirname, 'app/index.html'));
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
app.listen(PORT, () => {
|
| 40 |
+
console.log(`Server listening at http://localhost:${PORT}`);
|
| 41 |
+
});
|
tailwind.config.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('tailwindcss').Config} */
|
| 2 |
+
export default {
|
| 3 |
+
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
| 4 |
+
theme: {
|
| 5 |
+
extend: {},
|
| 6 |
+
},
|
| 7 |
+
plugins: [require('daisyui')],
|
| 8 |
+
};
|
tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ESNext",
|
| 4 |
+
"module": "ESNext",
|
| 5 |
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
| 6 |
+
"moduleResolution": "Node",
|
| 7 |
+
"strict": true,
|
| 8 |
+
"sourceMap": true,
|
| 9 |
+
"resolveJsonModule": true,
|
| 10 |
+
"esModuleInterop": true,
|
| 11 |
+
"types": ["vite/client", "node"],
|
| 12 |
+
"noEmit": true,
|
| 13 |
+
"noUnusedLocals": true,
|
| 14 |
+
"noUnusedParameters": true,
|
| 15 |
+
"noImplicitReturns": true,
|
| 16 |
+
"jsx": "react",
|
| 17 |
+
"allowJs": false,
|
| 18 |
+
"isolatedModules": true,
|
| 19 |
+
"skipLibCheck": false,
|
| 20 |
+
"allowSyntheticDefaultImports": true,
|
| 21 |
+
"forceConsistentCasingInFileNames": true
|
| 22 |
+
},
|
| 23 |
+
"include": ["./src/app"]
|
| 24 |
+
}
|
tsconfig.server.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"extends": "./tsconfig.json",
|
| 3 |
+
"compilerOptions": {
|
| 4 |
+
"outDir": "dist",
|
| 5 |
+
"module": "ES2022",
|
| 6 |
+
"target": "ES2022",
|
| 7 |
+
"noEmit": false
|
| 8 |
+
},
|
| 9 |
+
"include": ["src/*.ts"],
|
| 10 |
+
"exclude": ["src/app"]
|
| 11 |
+
}
|
vite.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import dotenv from 'dotenv';
|
| 2 |
+
dotenv.config();
|
| 3 |
+
|
| 4 |
+
import { defineConfig } from 'vite';
|
| 5 |
+
import reactRefresh from '@vitejs/plugin-react-refresh';
|
| 6 |
+
|
| 7 |
+
const { PORT = 7860 } = process.env;
|
| 8 |
+
|
| 9 |
+
// https://vitejs.dev/config/
|
| 10 |
+
export default defineConfig({
|
| 11 |
+
plugins: [reactRefresh()],
|
| 12 |
+
server: {
|
| 13 |
+
proxy: {
|
| 14 |
+
'/api': {
|
| 15 |
+
target: `http://localhost:${PORT}`,
|
| 16 |
+
changeOrigin: true,
|
| 17 |
+
},
|
| 18 |
+
},
|
| 19 |
+
},
|
| 20 |
+
build: {
|
| 21 |
+
outDir: 'dist/app',
|
| 22 |
+
},
|
| 23 |
+
});
|