fix(ai): fix database model mapping for AI matches and missing AI names
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback, useRef } from "react";
|
||||||
import { Chessboard } from "react-chessboard";
|
import { Chessboard } from "react-chessboard";
|
||||||
import { Chess } from "chess.js";
|
import { Chess } from "chess.js";
|
||||||
import { API_URL } from "@/config";
|
import { API_URL } from "@/config";
|
||||||
|
import { useSession } from "@/context/session";
|
||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
const AI_LEVELS = [
|
const AI_LEVELS = [
|
||||||
@@ -16,6 +17,7 @@ const AI_LEVELS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function AiGamePage() {
|
export default function AiGamePage() {
|
||||||
|
const { user } = useSession();
|
||||||
const [selectedLevel, setSelectedLevel] = useState(3);
|
const [selectedLevel, setSelectedLevel] = useState(3);
|
||||||
const [gameStarted, setGameStarted] = useState(false);
|
const [gameStarted, setGameStarted] = useState(false);
|
||||||
const [game, setGame] = useState(new Chess());
|
const [game, setGame] = useState(new Chess());
|
||||||
@@ -23,6 +25,9 @@ export default function AiGamePage() {
|
|||||||
const [status, setStatus] = useState("");
|
const [status, setStatus] = useState("");
|
||||||
const [thinking, setThinking] = useState(false);
|
const [thinking, setThinking] = useState(false);
|
||||||
const [lastMove, setLastMove] = useState<{ from: string; to: string } | null>(null);
|
const [lastMove, setLastMove] = useState<{ from: string; to: string } | null>(null);
|
||||||
|
const [savedGameId, setSavedGameId] = useState<number | null>(null);
|
||||||
|
const startedAtRef = useRef<number>(Date.now());
|
||||||
|
const gameSavedRef = useRef(false);
|
||||||
|
|
||||||
const getStatus = useCallback((g: Chess) => {
|
const getStatus = useCallback((g: Chess) => {
|
||||||
if (g.isCheckmate()) return g.turn() === "w" ? "Schwarz gewinnt! Schachmatt." : "Weiß gewinnt! Schachmatt.";
|
if (g.isCheckmate()) return g.turn() === "w" ? "Schwarz gewinnt! Schachmatt." : "Weiß gewinnt! Schachmatt.";
|
||||||
@@ -31,7 +36,31 @@ export default function AiGamePage() {
|
|||||||
return g.turn() === "w" ? "Weiß am Zug" : "Schwarz am Zug";
|
return g.turn() === "w" ? "Weiß am Zug" : "Schwarz am Zug";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const requestAiMove = useCallback(async (fen: string, level: number) => {
|
const saveGame = useCallback(async (g: Chess, color: "white" | "black", level: number) => {
|
||||||
|
if (!user?.id || typeof user.id !== "number") return;
|
||||||
|
if (gameSavedRef.current) return;
|
||||||
|
gameSavedRef.current = true;
|
||||||
|
|
||||||
|
const winner = g.isCheckmate() ? (g.turn() === "w" ? "black" : "white") : "draw";
|
||||||
|
const endReason = g.isCheckmate() ? "checkmate" : g.isStalemate() ? "stalemate" : g.isThreefoldRepetition() ? "repetition" : g.isInsufficientMaterial() ? "insufficient" : "draw";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_URL}/v1/ai/save`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ pgn: g.pgn(), winner, endReason, playerColor: color, level, startedAt: startedAtRef.current })
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const { id } = await res.json();
|
||||||
|
setSavedGameId(id);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Save game error:", e);
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const requestAiMove = useCallback(async (fen: string, level: number, color: "white" | "black") => {
|
||||||
setThinking(true);
|
setThinking(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}/v1/ai/move`, {
|
const res = await fetch(`${API_URL}/v1/ai/move`, {
|
||||||
@@ -48,6 +77,7 @@ export default function AiGamePage() {
|
|||||||
const result = newGame.move({ from: move.slice(0, 2), to: move.slice(2, 4), promotion: move[4] || "q" });
|
const result = newGame.move({ from: move.slice(0, 2), to: move.slice(2, 4), promotion: move[4] || "q" });
|
||||||
if (result) setLastMove({ from: result.from, to: result.to });
|
if (result) setLastMove({ from: result.from, to: result.to });
|
||||||
setStatus(getStatus(newGame));
|
setStatus(getStatus(newGame));
|
||||||
|
if (newGame.isGameOver()) saveGame(newGame, color, level);
|
||||||
return newGame;
|
return newGame;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -56,7 +86,7 @@ export default function AiGamePage() {
|
|||||||
console.error("AI move error:", e);
|
console.error("AI move error:", e);
|
||||||
}
|
}
|
||||||
setThinking(false);
|
setThinking(false);
|
||||||
}, [getStatus]);
|
}, [getStatus, saveGame]);
|
||||||
|
|
||||||
const onDrop = useCallback((sourceSquare: string, targetSquare: string) => {
|
const onDrop = useCallback((sourceSquare: string, targetSquare: string) => {
|
||||||
if (thinking) return false;
|
if (thinking) return false;
|
||||||
@@ -76,11 +106,13 @@ export default function AiGamePage() {
|
|||||||
setGame(newGame);
|
setGame(newGame);
|
||||||
setStatus(getStatus(newGame));
|
setStatus(getStatus(newGame));
|
||||||
|
|
||||||
if (!newGame.isGameOver()) {
|
if (newGame.isGameOver()) {
|
||||||
requestAiMove(newGame.fen(), selectedLevel);
|
saveGame(newGame, playerColor, selectedLevel);
|
||||||
|
} else {
|
||||||
|
requestAiMove(newGame.fen(), selectedLevel, playerColor);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}, [game, playerColor, thinking, selectedLevel, getStatus, requestAiMove]);
|
}, [game, playerColor, thinking, selectedLevel, getStatus, requestAiMove, saveGame]);
|
||||||
|
|
||||||
const startGame = (color: "white" | "black") => {
|
const startGame = (color: "white" | "black") => {
|
||||||
const newGame = new Chess();
|
const newGame = new Chess();
|
||||||
@@ -88,10 +120,13 @@ export default function AiGamePage() {
|
|||||||
setPlayerColor(color);
|
setPlayerColor(color);
|
||||||
setGameStarted(true);
|
setGameStarted(true);
|
||||||
setLastMove(null);
|
setLastMove(null);
|
||||||
|
setSavedGameId(null);
|
||||||
|
gameSavedRef.current = false;
|
||||||
|
startedAtRef.current = Date.now();
|
||||||
setStatus(getStatus(newGame));
|
setStatus(getStatus(newGame));
|
||||||
|
|
||||||
if (color === "black") {
|
if (color === "black") {
|
||||||
requestAiMove(newGame.fen(), selectedLevel);
|
requestAiMove(newGame.fen(), selectedLevel, color);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,6 +134,8 @@ export default function AiGamePage() {
|
|||||||
setGameStarted(false);
|
setGameStarted(false);
|
||||||
setGame(new Chess());
|
setGame(new Chess());
|
||||||
setLastMove(null);
|
setLastMove(null);
|
||||||
|
setSavedGameId(null);
|
||||||
|
gameSavedRef.current = false;
|
||||||
setStatus("");
|
setStatus("");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,6 +203,11 @@ export default function AiGamePage() {
|
|||||||
<div className={`card shadow p-4 ${game.isGameOver() ? "bg-warning text-warning-content" : "bg-base-200"}`}>
|
<div className={`card shadow p-4 ${game.isGameOver() ? "bg-warning text-warning-content" : "bg-base-200"}`}>
|
||||||
<p className="font-semibold">{status}</p>
|
<p className="font-semibold">{status}</p>
|
||||||
{thinking && <p className="text-sm opacity-70 mt-1">KI denkt nach...</p>}
|
{thinking && <p className="text-sm opacity-70 mt-1">KI denkt nach...</p>}
|
||||||
|
{savedGameId && (
|
||||||
|
<a className="btn btn-sm btn-ghost mt-2" href={`/archive/${savedGameId}`} target="_blank" rel="noopener noreferrer">
|
||||||
|
Spiel überprüfen
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className="btn btn-outline" onClick={resetGame}>Neues Spiel</button>
|
<button className="btn btn-outline" onClick={resetGame}>Neues Spiel</button>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export default async function Profile({ params }: { params: { name: string } })
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className="mb-1 text-lg font-bold">Recent games</h2>
|
<h2 className="mb-1 text-lg font-bold">Letzte Spiele</h2>
|
||||||
<ul className="bg-base-300 flex h-[60vh] flex-col gap-1 overflow-y-scroll rounded-lg">
|
<ul className="bg-base-300 flex h-[60vh] flex-col gap-1 overflow-y-scroll rounded-lg">
|
||||||
{data.recentGames.map((game) => {
|
{data.recentGames.map((game) => {
|
||||||
let endReason = game.endReason as string;
|
let endReason = game.endReason as string;
|
||||||
@@ -77,6 +77,9 @@ export default async function Profile({ params }: { params: { name: string } })
|
|||||||
key={game.id}
|
key={game.id}
|
||||||
className="border-base-100 flex flex-wrap items-center justify-between gap-8 border-b-2 p-3"
|
className="border-base-100 flex flex-wrap items-center justify-between gap-8 border-b-2 p-3"
|
||||||
>
|
>
|
||||||
|
{game.vsAi && (
|
||||||
|
<span className="badge badge-info badge-sm">vs KI Lv.{game.aiLevel}</span>
|
||||||
|
)}
|
||||||
<div className="flex w-72 justify-between">
|
<div className="flex w-72 justify-between">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="flex items-center gap-1 text-sm">
|
<span className="flex items-center gap-1 text-sm">
|
||||||
|
|||||||
5260
pnpm-lock.yaml
generated
Normal file
5260
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ export const AI_LEVELS: StockfishLevel[] = [
|
|||||||
export class StockfishEngine {
|
export class StockfishEngine {
|
||||||
private process: ChildProcess | null = null;
|
private process: ChildProcess | null = null;
|
||||||
private ready = false;
|
private ready = false;
|
||||||
private resolvers: Array<(move: string) => void> = [];
|
private resolvers: ((move: string) => void)[] = [];
|
||||||
private initPromise: Promise<void>;
|
private initPromise: Promise<void>;
|
||||||
private buffer = "";
|
private buffer = "";
|
||||||
|
|
||||||
|
|||||||
@@ -5,20 +5,16 @@ export const activeGames: Game[] = [];
|
|||||||
|
|
||||||
export const save = async (game: Game) => {
|
export const save = async (game: Game) => {
|
||||||
try {
|
try {
|
||||||
const white: User = {};
|
const white: User = { name: game.white?.name };
|
||||||
const black: User = {};
|
const black: User = { name: game.black?.name };
|
||||||
if (typeof game.white?.id === "string") {
|
if (typeof game.white?.id !== "string") {
|
||||||
white.name = game.white?.name;
|
|
||||||
} else {
|
|
||||||
white.id = game.white?.id;
|
white.id = game.white?.id;
|
||||||
}
|
}
|
||||||
if (typeof game.black?.id === "string") {
|
if (typeof game.black?.id !== "string") {
|
||||||
black.name = game.black?.name;
|
|
||||||
} else {
|
|
||||||
black.id = game.black?.id;
|
black.id = game.black?.id;
|
||||||
}
|
}
|
||||||
const res = await db.query(
|
const res = await db.query(
|
||||||
`INSERT INTO "game"(winner, end_reason, pgn, white_id, white_name, black_id, black_name, started_at) VALUES($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
|
`INSERT INTO "game"(winner, end_reason, pgn, white_id, white_name, black_id, black_name, started_at, vs_ai, ai_level) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *`,
|
||||||
[
|
[
|
||||||
game.winner || null,
|
game.winner || null,
|
||||||
game.endReason || null,
|
game.endReason || null,
|
||||||
@@ -27,7 +23,9 @@ export const save = async (game: Game) => {
|
|||||||
white.name || null,
|
white.name || null,
|
||||||
black.id || null,
|
black.id || null,
|
||||||
black.name || null,
|
black.name || null,
|
||||||
new Date(game.startedAt as number)
|
new Date(game.startedAt as number),
|
||||||
|
game.vsAi || false,
|
||||||
|
game.aiLevel || null
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
if (black.id || white.id) {
|
if (black.id || white.id) {
|
||||||
@@ -55,7 +53,7 @@ export const save = async (game: Game) => {
|
|||||||
return {
|
return {
|
||||||
id: res.rows[0].id,
|
id: res.rows[0].id,
|
||||||
winner: res.rows[0].winner,
|
winner: res.rows[0].winner,
|
||||||
endReason: res.rows[0].reason,
|
endReason: res.rows[0].end_reason,
|
||||||
pgn: res.rows[0].pgn,
|
pgn: res.rows[0].pgn,
|
||||||
white: {
|
white: {
|
||||||
id: res.rows[0].white_id || undefined,
|
id: res.rows[0].white_id || undefined,
|
||||||
@@ -66,7 +64,9 @@ export const save = async (game: Game) => {
|
|||||||
name: res.rows[0].black_name || undefined
|
name: res.rows[0].black_name || undefined
|
||||||
},
|
},
|
||||||
startedAt: res.rows[0].started_at.getTime(),
|
startedAt: res.rows[0].started_at.getTime(),
|
||||||
endedAt: res.rows[0].ended_at?.getTime() || undefined
|
endedAt: res.rows[0].ended_at?.getTime() || undefined,
|
||||||
|
vsAi: res.rows[0].vs_ai || undefined,
|
||||||
|
aiLevel: res.rows[0].ai_level || undefined
|
||||||
} as Game;
|
} as Game;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -77,7 +77,7 @@ export const save = async (game: Game) => {
|
|||||||
export const findById = async (id: number) => {
|
export const findById = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
const res = await db.query(
|
const res = await db.query(
|
||||||
`SELECT game.id, game.winner, game.end_reason, game.pgn, white_user.id AS white_id, COALESCE(white_user.name, game.white_name) AS white_name, black_user.id AS black_id, started_at, ended_at, COALESCE(black_user.name, game.black_name) AS black_name FROM game LEFT JOIN "user" white_user ON white_user.id = game.white_id LEFT JOIN "user" black_user ON black_user.id = game.black_id WHERE game.id=$1`,
|
`SELECT game.id, game.winner, game.end_reason, game.pgn, game.vs_ai, game.ai_level, white_user.id AS white_id, COALESCE(white_user.name, game.white_name) AS white_name, black_user.id AS black_id, started_at, ended_at, COALESCE(black_user.name, game.black_name) AS black_name FROM game LEFT JOIN "user" white_user ON white_user.id = game.white_id LEFT JOIN "user" black_user ON black_user.id = game.black_id WHERE game.id=$1`,
|
||||||
[id]
|
[id]
|
||||||
);
|
);
|
||||||
if (res.rowCount) {
|
if (res.rowCount) {
|
||||||
@@ -89,7 +89,9 @@ export const findById = async (id: number) => {
|
|||||||
white: { id: res.rows[0].white_id || undefined, name: res.rows[0].white_name },
|
white: { id: res.rows[0].white_id || undefined, name: res.rows[0].white_name },
|
||||||
black: { id: res.rows[0].black_id || undefined, name: res.rows[0].black_name },
|
black: { id: res.rows[0].black_id || undefined, name: res.rows[0].black_name },
|
||||||
startedAt: res.rows[0].started_at.getTime(),
|
startedAt: res.rows[0].started_at.getTime(),
|
||||||
endedAt: res.rows[0].ended_at?.getTime() || undefined
|
endedAt: res.rows[0].ended_at?.getTime() || undefined,
|
||||||
|
vsAi: res.rows[0].vs_ai || undefined,
|
||||||
|
aiLevel: res.rows[0].ai_level || undefined
|
||||||
} as Game;
|
} as Game;
|
||||||
} else return null;
|
} else return null;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -105,7 +107,7 @@ export const findByUserId = async (id: number, limit = 10) => {
|
|||||||
try {
|
try {
|
||||||
// TODO: pagination
|
// TODO: pagination
|
||||||
const res = await db.query(
|
const res = await db.query(
|
||||||
`SELECT game.id, game.winner, game.end_reason, game.pgn, white_user.id AS white_id, COALESCE(white_user.name, game.white_name) AS white_name, black_user.id AS black_id, started_at, ended_at, COALESCE(black_user.name, game.black_name) AS black_name FROM game LEFT JOIN "user" white_user ON white_user.id = game.white_id LEFT JOIN "user" black_user ON black_user.id = game.black_id WHERE white_user.id=$1 OR black_user.id=$1 ORDER BY id DESC LIMIT $2`,
|
`SELECT game.id, game.winner, game.end_reason, game.pgn, game.vs_ai, game.ai_level, white_user.id AS white_id, COALESCE(white_user.name, game.white_name) AS white_name, black_user.id AS black_id, started_at, ended_at, COALESCE(black_user.name, game.black_name) AS black_name FROM game LEFT JOIN "user" white_user ON white_user.id = game.white_id LEFT JOIN "user" black_user ON black_user.id = game.black_id WHERE white_user.id=$1 OR black_user.id=$1 ORDER BY id DESC LIMIT $2`,
|
||||||
[id, limit]
|
[id, limit]
|
||||||
);
|
);
|
||||||
return res.rows.map((r) => {
|
return res.rows.map((r) => {
|
||||||
@@ -117,7 +119,9 @@ export const findByUserId = async (id: number, limit = 10) => {
|
|||||||
white: { id: r.white_id || undefined, name: r.white_name },
|
white: { id: r.white_id || undefined, name: r.white_name },
|
||||||
black: { id: r.black_id || undefined, name: r.black_name },
|
black: { id: r.black_id || undefined, name: r.black_name },
|
||||||
startedAt: r.started_at.getTime(),
|
startedAt: r.started_at.getTime(),
|
||||||
endedAt: r.ended_at?.getTime() || undefined
|
endedAt: r.ended_at?.getTime() || undefined,
|
||||||
|
vsAi: r.vs_ai || undefined,
|
||||||
|
aiLevel: r.ai_level || undefined
|
||||||
} as Game;
|
} as Game;
|
||||||
});
|
});
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -137,7 +141,9 @@ export const remove = async (id: number) => {
|
|||||||
white: { id: res.rows[0].white_id, name: res.rows[0].white_name },
|
white: { id: res.rows[0].white_id, name: res.rows[0].white_name },
|
||||||
black: { id: res.rows[0].black_id, name: res.rows[0].black_name },
|
black: { id: res.rows[0].black_id, name: res.rows[0].black_name },
|
||||||
startedAt: res.rows[0].started_at.getTime(),
|
startedAt: res.rows[0].started_at.getTime(),
|
||||||
endedAt: res.rows[0].ended_at?.getTime() || undefined
|
endedAt: res.rows[0].ended_at?.getTime() || undefined,
|
||||||
|
vsAi: res.rows[0].vs_ai || undefined,
|
||||||
|
aiLevel: res.rows[0].ai_level || undefined
|
||||||
} as Game;
|
} as Game;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { AI_LEVELS, getEngine } from "../controllers/stockfish.controller.js";
|
import { AI_LEVELS, getEngine } from "../controllers/stockfish.controller.js";
|
||||||
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
|
import GameModel from "../db/models/game.model.js";
|
||||||
|
import type { Game } from "@michess/types";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -28,4 +31,40 @@ router.get("/levels", (_req: Request, res: Response) => {
|
|||||||
res.status(200).json(AI_LEVELS.map(({ level, name }) => ({ level, name })));
|
res.status(200).json(AI_LEVELS.map(({ level, name }) => ({ level, name })));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/save", requireAuth, async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { pgn, winner, endReason, playerColor, level, startedAt } = req.body;
|
||||||
|
const user = req.session.user!;
|
||||||
|
|
||||||
|
if (!pgn || !winner || !endReason || !playerColor) {
|
||||||
|
res.status(400).json({ message: "Missing required fields." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const humanPlayer = { id: user.id as number, name: user.name };
|
||||||
|
const aiPlayer = { name: `Stockfish` };
|
||||||
|
|
||||||
|
const game: Game = {
|
||||||
|
pgn,
|
||||||
|
winner,
|
||||||
|
endReason,
|
||||||
|
white: playerColor === "white" ? humanPlayer : aiPlayer,
|
||||||
|
black: playerColor === "black" ? humanPlayer : aiPlayer,
|
||||||
|
startedAt: startedAt || Date.now(),
|
||||||
|
vsAi: true,
|
||||||
|
aiLevel: level || null
|
||||||
|
};
|
||||||
|
|
||||||
|
const saved = await GameModel.save(game);
|
||||||
|
if (!saved) {
|
||||||
|
res.status(500).json({ message: "Failed to save game." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(200).json({ id: saved.id });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ app.use("/v1", (req: Request, res: Response, next: NextFunction) => {
|
|||||||
if (req.session.user?.id && typeof req.session.user.id === "number") {
|
if (req.session.user?.id && typeof req.session.user.id === "number") {
|
||||||
const user = req.session.user as { banned?: boolean };
|
const user = req.session.user as { banned?: boolean };
|
||||||
if (user.banned) {
|
if (user.banned) {
|
||||||
req.session.destroy(() => {});
|
req.session.destroy((err) => { if (err) console.error(err); });
|
||||||
res.status(403).json({ message: "Your account has been banned." });
|
res.status(403).json({ message: "Your account has been banned." });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user