chore: reorganize files and add some boilerplate

This commit is contained in:
Cozma Rares
2023-07-24 12:06:42 +03:00
parent 7d6dc492c3
commit 76032237d4
12 changed files with 591 additions and 244 deletions
+2
View File
@@ -0,0 +1,2 @@
NODE_ENV=development
PORT=5000
+2
View File
@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.env
+5 -1
View File
@@ -9,8 +9,12 @@
"preview": "vite preview"
},
"dependencies": {
"localforage": "^1.10.0",
"match-sorter": "^6.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.2",
"sort-by": "^1.2.0"
},
"devDependencies": {
"@types/react": "^18.0.28",
+89
View File
@@ -5,12 +5,24 @@ settings:
excludeLinksFromLockfile: false
dependencies:
localforage:
specifier: ^1.10.0
version: 1.10.0
match-sorter:
specifier: ^6.3.1
version: 6.3.1
react:
specifier: ^18.2.0
version: 18.2.0
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
react-router-dom:
specifier: ^6.14.2
version: 6.14.2(react-dom@18.2.0)(react@18.2.0)
sort-by:
specifier: ^1.2.0
version: 1.2.0
devDependencies:
'@types/react':
@@ -45,6 +57,13 @@ packages:
engines: {node: '>=10'}
dev: true
/@babel/runtime@7.22.6:
resolution: {integrity: sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
dev: false
/@esbuild/android-arm64@0.17.14:
resolution: {integrity: sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg==}
engines: {node: '>=12'}
@@ -298,6 +317,11 @@ packages:
fastq: 1.15.0
dev: true
/@remix-run/router@1.7.2:
resolution: {integrity: sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==}
engines: {node: '>=14'}
dev: false
/@swc/core-darwin-arm64@1.3.44:
resolution: {integrity: sha512-Y+oVsCjXUPvr3D9YLuB1gjP84TseM/CRkbPNrf+3JXQhsPEkgxdIdFP1cl/obeqMQrRgPpvSfK+TOvGuOuV22g==}
engines: {node: '>=10'}
@@ -668,6 +692,10 @@ packages:
function-bind: 1.1.1
dev: true
/immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
dev: false
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
@@ -718,6 +746,12 @@ packages:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: false
/lie@3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
dependencies:
immediate: 3.0.6
dev: false
/lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
@@ -727,6 +761,12 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
/localforage@1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
dependencies:
lie: 3.1.1
dev: false
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@@ -734,6 +774,13 @@ packages:
js-tokens: 4.0.0
dev: false
/match-sorter@6.3.1:
resolution: {integrity: sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==}
dependencies:
'@babel/runtime': 7.22.6
remove-accents: 0.4.2
dev: false
/merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -791,6 +838,11 @@ packages:
engines: {node: '>= 6'}
dev: true
/object-path@0.6.0:
resolution: {integrity: sha512-fxrwsCFi3/p+LeLOAwo/wyRMODZxdGBtUlWRzsEpsUVrisZbEfZ21arxLGfaWfcnqb8oHPNihIb4XPE8CQPN5A==}
engines: {node: '>=0.8.0'}
dev: false
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -909,6 +961,29 @@ packages:
scheduler: 0.23.0
dev: false
/react-router-dom@6.14.2(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
dependencies:
'@remix-run/router': 1.7.2
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-router: 6.14.2(react@18.2.0)
dev: false
/react-router@6.14.2(react@18.2.0):
resolution: {integrity: sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8'
dependencies:
'@remix-run/router': 1.7.2
react: 18.2.0
dev: false
/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
@@ -929,6 +1004,14 @@ packages:
picomatch: 2.3.1
dev: true
/regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: false
/remove-accents@0.4.2:
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==}
dev: false
/resolve@1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
@@ -972,6 +1055,12 @@ packages:
loose-envify: 1.4.0
dev: false
/sort-by@1.2.0:
resolution: {integrity: sha512-aRyW65r3xMnf4nxJRluCg0H/woJpksU1dQxRtXYzau30sNBOmf5HACpDd9MZDhKh7ALQ5FgSOfMPwZEtUmMqcg==}
dependencies:
object-path: 0.6.0
dev: false
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
+5 -230
View File
@@ -1,43 +1,8 @@
import wp from "./assets/pieces/wp.png";
import wn from "./assets/pieces/wn.png";
import wb from "./assets/pieces/wb.png";
import wr from "./assets/pieces/wr.png";
import wq from "./assets/pieces/wq.png";
import wk from "./assets/pieces/wk.png";
import Chess, { Move } from "../../server/src/chess/engine";
import { useState } from "react";
import ChessBoard from "./components/Chessboard";
import bp from "./assets/pieces/bp.png";
import bn from "./assets/pieces/bn.png";
import bb from "./assets/pieces/bb.png";
import br from "./assets/pieces/br.png";
import bq from "./assets/pieces/bq.png";
import bk from "./assets/pieces/bk.png";
import Chess, {
squareColor,
Piece,
PieceType,
Color,
algebraic,
file,
rank,
FILE,
RANK,
squareIndex,
Move,
MOVE_FLAGS,
PIECE_PROMOTION,
COLOR,
PiecePromotionType,
} from "../../server/src/chess/engine";
import { MouseEventHandler, useLayoutEffect, useState } from "react";
import Show from "./utils/Show";
const PIECES: Record<Color, Record<PieceType, string>> = {
w: { p: wp, n: wn, b: wb, r: wr, q: wq, k: wk },
b: { p: bp, n: bn, b: bb, r: br, q: bq, k: bk },
};
export default function App() {
const App = () => {
const [chess] = useState(Chess.load());
const [, setUpdate] = useState(false);
@@ -58,196 +23,6 @@ export default function App() {
</button>
</>
);
}
// taken form https://github.com/uidotdev/usehooks
function useWindowSize() {
const [size, setSize] = useState({
width: -1,
height: -1,
});
useLayoutEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
handleResize();
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return size;
}
const ChessBoard: React.FC<{
chess: Chess;
sendMove: (move: Move) => void;
blackPerspective?: boolean;
}> = ({ chess, sendMove, blackPerspective }) => {
const { width, height } = useWindowSize();
const [activeTile, setActiveTile] = useState<number>(-1);
const [promotionMove, setPromotionMove] = useState<
(Pick<Move, "from" | "to"> & { color: Color }) | null
>(null);
const gridSize = Math.min(width, height, 800);
const tileProps = new Array(64).fill(null).map((_, i) => ({
tileNumber: i,
piece: chess.getPiece(i),
isAttacked: false,
isPromotion: false,
isActive: false,
}));
if (activeTile != -1) {
tileProps[activeTile].isActive = true;
chess.getMovesForSquare(algebraic(activeTile)).forEach(({ to, flags }) => {
const square = squareIndex(to);
tileProps[square].isAttacked = true;
tileProps[square].isPromotion =
flags & MOVE_FLAGS.PROMOTION ? true : false;
});
}
const tiles = tileProps.map((props, i) => (
<Tile key={i} {...props} blackPerspective={blackPerspective} />
));
const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
if (promotionMove != null) return;
// TODO: fix types
// @ts-ignore
const tile = parseInt(e.target.dataset.tile);
if (isNaN(tile)) return;
if (activeTile != -1 && tileProps[tile].isAttacked) {
const moveObj = {
from: algebraic(activeTile),
to: algebraic(tile),
color: (chess.getPiece(activeTile) as Piece).color,
};
if (tileProps[tile].isPromotion) return setPromotionMove(moveObj);
sendMove(moveObj);
setActiveTile(-1);
}
if (tileProps[tile].piece == null) return setActiveTile(-1);
if (tile == activeTile) setActiveTile(-1);
else setActiveTile(tile);
};
const sendPromotionMove = (promotion: PiecePromotionType) => {
if (promotionMove == null) return;
const moveObj: Move = {
from: promotionMove.from,
to: promotionMove.to,
promotion,
};
sendMove(moveObj);
setPromotionMove(null);
setActiveTile(-1);
};
return (
<div className="flex flex-row h-fit items-center">
<div
className="relative grid grid-rows-8 grid-cols-8 aspect-square border-8 border-black rounded-lg"
onClick={handleClick}
style={{ width: `${gridSize}px` }}
>
{blackPerspective == true ? tiles.reverse() : tiles}
</div>
{promotionMove == null ? (
<></>
) : (
<div className="w-fit flex flex-col bg-black rounded-r-2xl p-1">
{PIECE_PROMOTION.map((p) => (
<img
key={p}
src={PIECES[promotionMove.color][p]}
style={{ width: `${gridSize / 8}px`, aspectRatio: 1 }}
onClick={() => sendPromotionMove(p)}
/>
))}
</div>
)}
</div>
);
};
const TILE_COLORS = Object.freeze({
w: {
bg: "bg-white-tile",
text: "text-black-tile",
active: "bg-sky-400",
},
b: {
bg: "bg-black-tile",
text: "text-white-tile",
active: "bg-sky-700",
},
} as const);
const Tile: React.FC<{
tileNumber: number;
piece: Piece | null;
isActive: boolean;
isAttacked: boolean;
blackPerspective?: boolean;
}> = ({ tileNumber, piece, isActive, isAttacked, blackPerspective }) => {
const color = squareColor(tileNumber);
const { bg: bgColor, text: textColor } = TILE_COLORS[color];
const activeColor = isActive ? TILE_COLORS[color].active : "";
const tileFile = file(tileNumber);
const tileRank = rank(tileNumber);
const square = algebraic(tileNumber);
const isFirstColumn = tileFile == (blackPerspective ? FILE.H : FILE.A);
const isLastRow = tileRank == (blackPerspective ? RANK.EIGHTH : RANK.FIRST);
return (
<div
className={`relative aspect-square font-bold text-xl isolate group [&>*]:pointer-events-none ${bgColor} ${activeColor}`}
data-tile={tileNumber}
>
{piece == null ? <></> : <img src={PIECES[piece.color][piece.type]} />}
<Show when={isAttacked == true}>
<Show
when={piece == null}
fallback={
<div className="absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-full aspect-square border-8 border-gray-900/40 rounded-full"></div>
}
>
<div className="absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-[35%] aspect-square bg-gray-900/40 rounded-full group-hover:w-[45%]"></div>
</Show>
</Show>
<Show when={isFirstColumn}>
<div className={`absolute top-1 left-1 -z-10 ${textColor}`}>
{square[1]}
</div>
</Show>
<Show when={isLastRow}>
<div className={`absolute bottom-1 right-1 -z-10 ${textColor}`}>
{square[0]}
</div>
</Show>
</div>
);
};
export default App;
+206
View File
@@ -0,0 +1,206 @@
import wp from "../assets/pieces/wp.png";
import wn from "../assets/pieces/wn.png";
import wb from "../assets/pieces/wb.png";
import wr from "../assets/pieces/wr.png";
import wq from "../assets/pieces/wq.png";
import wk from "../assets/pieces/wk.png";
import bp from "../assets/pieces/bp.png";
import bn from "../assets/pieces/bn.png";
import bb from "../assets/pieces/bb.png";
import br from "../assets/pieces/br.png";
import bq from "../assets/pieces/bq.png";
import bk from "../assets/pieces/bk.png";
import Chess, {
squareColor,
Piece,
PieceType,
Color,
algebraic,
file,
rank,
FILE,
RANK,
squareIndex,
Move,
MOVE_FLAGS,
PIECE_PROMOTION,
PiecePromotionType,
} from "../../../server/src/chess/engine";
import { MouseEventHandler, useState } from "react";
import Show from "../utils/Show";
import useWindowSize from "../utils/useWindowSize";
const PIECES: Record<Color, Record<PieceType, string>> = {
w: { p: wp, n: wn, b: wb, r: wr, q: wq, k: wk },
b: { p: bp, n: bn, b: bb, r: br, q: bq, k: bk },
};
const ChessBoard: React.FC<{
chess: Chess;
sendMove: (move: Move) => void;
blackPerspective?: boolean;
}> = ({ chess, sendMove, blackPerspective }) => {
const { width, height } = useWindowSize();
const [activeTile, setActiveTile] = useState<number>(-1);
const [promotionMove, setPromotionMove] = useState<
(Pick<Move, "from" | "to"> & { color: Color }) | null
>(null);
const gridSize = Math.min(width, height, 800);
const tileProps = new Array(64).fill(null).map((_, i) => ({
tileNumber: i,
piece: chess.getPiece(i),
isAttacked: false,
isPromotion: false,
isActive: false,
}));
if (activeTile != -1) {
tileProps[activeTile].isActive = true;
chess.getMovesForSquare(algebraic(activeTile)).forEach(({ to, flags }) => {
const square = squareIndex(to);
tileProps[square].isAttacked = true;
tileProps[square].isPromotion =
flags & MOVE_FLAGS.PROMOTION ? true : false;
});
}
const tiles = tileProps.map((props, i) => (
<Tile key={i} {...props} blackPerspective={blackPerspective} />
));
const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
if (promotionMove != null) return;
// TODO: fix types
// @ts-ignore
const tile = parseInt(e.target.dataset.tile);
if (isNaN(tile)) return;
if (activeTile != -1 && tileProps[tile].isAttacked) {
const moveObj = {
from: algebraic(activeTile),
to: algebraic(tile),
color: (chess.getPiece(activeTile) as Piece).color,
};
if (tileProps[tile].isPromotion) return setPromotionMove(moveObj);
sendMove(moveObj);
setActiveTile(-1);
}
if (tileProps[tile].piece == null) return setActiveTile(-1);
if (tile == activeTile) setActiveTile(-1);
else setActiveTile(tile);
};
const sendPromotionMove = (promotion: PiecePromotionType) => {
if (promotionMove == null) return;
const moveObj: Move = {
from: promotionMove.from,
to: promotionMove.to,
promotion,
};
sendMove(moveObj);
setPromotionMove(null);
setActiveTile(-1);
};
return (
<div className="flex flex-row h-fit items-center">
<div
className="relative grid grid-rows-8 grid-cols-8 aspect-square border-8 border-black rounded-lg"
onClick={handleClick}
style={{ width: `${gridSize}px` }}
>
{blackPerspective == true ? tiles.reverse() : tiles}
</div>
{promotionMove == null ? (
<></>
) : (
<div className="w-fit flex flex-col bg-black rounded-r-2xl p-1">
{PIECE_PROMOTION.map((p) => (
<img
key={p}
src={PIECES[promotionMove.color][p]}
style={{ width: `${gridSize / 8}px`, aspectRatio: 1 }}
onClick={() => sendPromotionMove(p)}
/>
))}
</div>
)}
</div>
);
};
const TILE_COLORS = Object.freeze({
w: {
bg: "bg-white-tile",
text: "text-black-tile",
active: "bg-sky-400",
},
b: {
bg: "bg-black-tile",
text: "text-white-tile",
active: "bg-sky-700",
},
} as const);
const Tile: React.FC<{
tileNumber: number;
piece: Piece | null;
isActive: boolean;
isAttacked: boolean;
blackPerspective?: boolean;
}> = ({ tileNumber, piece, isActive, isAttacked, blackPerspective }) => {
const color = squareColor(tileNumber);
const { bg: bgColor, text: textColor } = TILE_COLORS[color];
const activeColor = isActive ? TILE_COLORS[color].active : "";
const tileFile = file(tileNumber);
const tileRank = rank(tileNumber);
const square = algebraic(tileNumber);
const isFirstColumn = tileFile == (blackPerspective ? FILE.H : FILE.A);
const isLastRow = tileRank == (blackPerspective ? RANK.EIGHTH : RANK.FIRST);
return (
<div
className={`relative aspect-square font-bold text-xl isolate group [&>*]:pointer-events-none ${bgColor} ${activeColor}`}
data-tile={tileNumber}
>
{piece == null ? <></> : <img src={PIECES[piece.color][piece.type]} />}
<Show when={isAttacked == true}>
<Show
when={piece == null}
fallback={
<div className="absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-full aspect-square border-8 border-gray-900/40 rounded-full"></div>
}
>
<div className="absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-[35%] aspect-square bg-gray-900/40 rounded-full group-hover:w-[45%]"></div>
</Show>
</Show>
<Show when={isFirstColumn}>
<div className={`absolute top-1 left-1 -z-10 ${textColor}`}>
{square[1]}
</div>
</Show>
<Show when={isLastRow}>
<div className={`absolute bottom-1 right-1 -z-10 ${textColor}`}>
{square[0]}
</div>
</Show>
</div>
);
};
export default ChessBoard;
+27
View File
@@ -0,0 +1,27 @@
import { useLayoutEffect, useState } from "react";
// taken form https://github.com/uidotdev/usehooks
export default function useWindowSize() {
const [size, setSize] = useState({
width: -1,
height: -1,
});
useLayoutEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
handleResize();
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return size;
}
+12 -3
View File
@@ -1,7 +1,16 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
server: {
port: 3000,
proxy: {
"/api": {
target: "http://localhost:5000",
changeOrigin: true,
},
},
},
});
+10 -4
View File
@@ -4,23 +4,29 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "node dist/server.js",
"dev": "nodemon src/server.ts",
"build": "tsc -p .",
"start": "node server/dist/server.js",
"server": "nodemon server/src/server.ts",
"client": "npm run dev --prefix client",
"dev": "concurrently \"npm run server\" \"npm run client\"",
"build:server": "tsc -p .",
"build:client":"npm run build --prefix client",
"build": "concurrently \"npm run build:server\" \"npm run build:client\"",
"test": "vitest run"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^16.3.1",
"express": "^4.18.2"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/node": "^18.15.11",
"concurrently": "^8.2.0",
"nodemon": "^2.0.22",
"ts-node": "^10.9.1",
"typescript": "^5.0.3",
"vitest": "^0.29.8"
}
}
}
+202
View File
@@ -1,6 +1,13 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
dotenv:
specifier: ^16.3.1
version: 16.3.1
express:
specifier: ^4.18.2
version: 4.18.2
@@ -12,6 +19,9 @@ devDependencies:
'@types/node':
specifier: ^18.15.11
version: 18.15.11
concurrently:
specifier: ^8.2.0
version: 8.2.0
nodemon:
specifier: ^2.0.22
version: 2.0.22
@@ -27,6 +37,13 @@ devDependencies:
packages:
/@babel/runtime@7.22.6:
resolution: {integrity: sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
dev: true
/@cspotcode/source-map-support@0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -391,6 +408,13 @@ packages:
engines: {node: '>=12'}
dev: true
/ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: true
/ansi-styles@5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
@@ -494,6 +518,14 @@ packages:
type-detect: 4.0.8
dev: true
/chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: true
/check-error@1.0.2:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
dev: true
@@ -521,10 +553,46 @@ packages:
string-width: 5.1.2
dev: true
/cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: true
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/concurrently@8.2.0:
resolution: {integrity: sha512-nnLMxO2LU492mTUj9qX/az/lESonSZu81UznYDoXtz1IQf996ixVqPAgHXwvHiHCAef/7S8HIK+fTFK7Ifk8YA==}
engines: {node: ^14.13.0 || >=16.0.0}
hasBin: true
dependencies:
chalk: 4.1.2
date-fns: 2.30.0
lodash: 4.17.21
rxjs: 7.8.1
shell-quote: 1.8.1
spawn-command: 0.0.2
supports-color: 8.1.1
tree-kill: 1.2.2
yargs: 17.7.2
dev: true
/content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
@@ -550,6 +618,13 @@ packages:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
dependencies:
'@babel/runtime': 7.22.6
dev: true
/debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@@ -612,6 +687,11 @@ packages:
engines: {node: '>=0.3.1'}
dev: true
/dotenv@16.3.1:
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
engines: {node: '>=12'}
dev: false
/eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
@@ -620,6 +700,10 @@ packages:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
/emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
dev: true
@@ -659,6 +743,11 @@ packages:
'@esbuild/win32-x64': 0.17.15
dev: true
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
dev: true
/escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
dev: false
@@ -750,6 +839,11 @@ packages:
/function-bind@1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
/get-func-name@2.0.0:
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
dev: true
@@ -774,6 +868,11 @@ packages:
engines: {node: '>=4'}
dev: true
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
dev: true
/has-symbols@1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
@@ -834,6 +933,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: true
/is-fullwidth-code-point@4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
engines: {node: '>=12'}
@@ -860,6 +964,10 @@ packages:
engines: {node: '>=14'}
dev: true
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/loupe@2.3.6:
resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
dependencies:
@@ -1087,6 +1195,15 @@ packages:
picomatch: 2.3.1
dev: true
/regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: true
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
/resolve@1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
@@ -1104,6 +1221,12 @@ packages:
fsevents: 2.3.2
dev: true
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
dependencies:
tslib: 2.6.0
dev: true
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
@@ -1159,6 +1282,10 @@ packages:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
dev: false
/shell-quote@1.8.1:
resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
dev: true
/side-channel@1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
@@ -1196,6 +1323,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/spawn-command@0.0.2:
resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
dev: true
/stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
dev: true
@@ -1209,6 +1340,15 @@ packages:
resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==}
dev: true
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: true
/string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
@@ -1218,6 +1358,13 @@ packages:
strip-ansi: 7.0.1
dev: true
/strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
dev: true
/strip-ansi@7.0.1:
resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
engines: {node: '>=12'}
@@ -1238,6 +1385,20 @@ packages:
has-flag: 3.0.0
dev: true
/supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-color@8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
@@ -1276,6 +1437,11 @@ packages:
nopt: 1.0.10
dev: true
/tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
dev: true
/ts-node@10.9.1(@types/node@18.15.11)(typescript@5.0.3):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
@@ -1307,6 +1473,10 @@ packages:
yn: 3.1.1
dev: true
/tslib@2.6.0:
resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==}
dev: true
/type-detect@4.0.8:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'}
@@ -1481,6 +1651,38 @@ packages:
stackback: 0.0.2
dev: true
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: true
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: true
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: true
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
dependencies:
cliui: 8.0.1
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
dev: true
/yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
+28 -3
View File
@@ -1,11 +1,36 @@
import express, { Application, Request, Response } from "express";
import path from "path";
import dotenv from "dotenv";
const PORT = process.env.PORT || 3000;
dotenv.config();
const PORT = process.env.PORT || 5000;
const app: Application = express();
app.get("/", (req: Request, res: Response) => {
res.send("Hello, world!");
if (process.env.NODE_ENV === "production") {
const __dirname = path.resolve();
app.use(express.static(path.join(__dirname, "client", "dist")));
app.get("*", (_req, res) =>
res.sendFile(path.resolve(__dirname, "client", "dist", "index.html"))
);
} else app.get("/", (_req, res) => res.send("Hello, world!"));
app.use((req, res, next) => {
const error = new Error(`Not Found - ${req.originalUrl}`);
res.status(404);
next(error);
});
app.use((err: Error, _req: Request, res: Response) => {
let status = res.statusCode == 200 ? 500 : res.statusCode;
let message = err.message;
res.status(status).json({
message,
stack: process.env.NODE_ENV !== "production" ? err.stack : null,
});
});
app.listen(PORT, () =>
+3 -3
View File
@@ -49,7 +49,7 @@
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
"outDir": "./server/dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
@@ -101,9 +101,9 @@
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": [
"src/__test__/"
"server/src/__test__/"
],
"include": [
"src/"
"server/src/"
]
}