@@ -0,0 +1,259 @@
|
||||
require("dotenv").config();
|
||||
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const path = require("path");
|
||||
const db = require("./src/db");
|
||||
const auth = require("./src/auth");
|
||||
const labels = require("./src/labels");
|
||||
const fmt = require("./src/format");
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
app.set("view engine", "ejs");
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.SESSION_SECRET || "change-me-in-production",
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||
sameSite: "lax",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.locals.nav = [
|
||||
{ href: "/", label: "Dashboard", key: "dashboard" },
|
||||
{ href: "/players", label: "Joueurs", key: "players" },
|
||||
{ href: "/market", label: "Marché", key: "market" },
|
||||
{ href: "/profile", label: "Mon profil", key: "profile" },
|
||||
];
|
||||
res.locals.fmt = fmt;
|
||||
res.locals.auth = auth;
|
||||
res.locals.dbStatus = db.getDbStatus();
|
||||
res.locals.sessionUser = req.session.playerUuid
|
||||
? {
|
||||
uuid: req.session.playerUuid,
|
||||
displayName: req.session.displayName,
|
||||
}
|
||||
: null;
|
||||
next();
|
||||
});
|
||||
|
||||
function enrichPlayer(player) {
|
||||
if (!player) return null;
|
||||
|
||||
const classLabel = labels.labelClass(
|
||||
player.class_id,
|
||||
db.getReferenceLabel("classes", player.class_id),
|
||||
);
|
||||
const raceLabel = labels.labelRace(
|
||||
player.race_id,
|
||||
db.getReferenceLabel("races", player.race_id),
|
||||
);
|
||||
const jobs = labels.parseJsonArray(player.jobs).map(labels.labelJob);
|
||||
const powers = labels.parseJsonArray(player.powers).map(labels.labelPower);
|
||||
|
||||
return {
|
||||
...player,
|
||||
classLabel,
|
||||
raceLabel,
|
||||
jobs,
|
||||
powers,
|
||||
isOnline: player.is_connected === 1,
|
||||
};
|
||||
}
|
||||
|
||||
function requireLogin(req, res, next) {
|
||||
if (!req.session.playerUuid) {
|
||||
return res.redirect("/profile");
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
const stats = db.getDashboardStats();
|
||||
const onlinePlayers = db.getOnlinePlayers().map(enrichPlayer);
|
||||
const recentPlayers = db.getRecentPlayers().map(enrichPlayer);
|
||||
|
||||
res.render("index", {
|
||||
page: "dashboard",
|
||||
title: "Dashboard",
|
||||
stats,
|
||||
onlinePlayers,
|
||||
recentPlayers,
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/players", (req, res) => {
|
||||
const players = db.getAllPlayers().map(enrichPlayer);
|
||||
|
||||
res.render("players/list", {
|
||||
page: "players",
|
||||
title: "Joueurs",
|
||||
players,
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/players/:uuid", (req, res) => {
|
||||
const player = enrichPlayer(db.getPlayerByUuid(req.params.uuid));
|
||||
if (!player) {
|
||||
return res.status(404).render("error", {
|
||||
page: "players",
|
||||
title: "Joueur introuvable",
|
||||
message: "Ce joueur n'existe pas dans la base de données.",
|
||||
});
|
||||
}
|
||||
|
||||
const group = db.getGroupForPlayer(player.uuid);
|
||||
|
||||
res.render("players/profile", {
|
||||
page: "players",
|
||||
title: player.display_name,
|
||||
player,
|
||||
group,
|
||||
comingSoon: true,
|
||||
isOwner: req.session.playerUuid === player.uuid,
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/market", (req, res) => {
|
||||
res.render("coming-soon", {
|
||||
page: "market",
|
||||
title: "Marché",
|
||||
heading: "Marché du serveur",
|
||||
description:
|
||||
"La place de marché permettra bientôt d'échanger des objets et consulter les annonces entre joueurs.",
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/profile", (req, res) => {
|
||||
if (req.session.playerUuid) {
|
||||
const player = enrichPlayer(db.getPlayerByUuid(req.session.playerUuid));
|
||||
if (!player) {
|
||||
req.session.destroy(() => {
|
||||
res.redirect("/profile");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const group = db.getGroupForPlayer(player.uuid);
|
||||
|
||||
return res.render("profile/account", {
|
||||
page: "profile",
|
||||
title: "Mon profil",
|
||||
player,
|
||||
group,
|
||||
});
|
||||
}
|
||||
|
||||
res.render("profile/login", {
|
||||
page: "profile",
|
||||
title: "Connexion",
|
||||
error: null,
|
||||
pseudo: "",
|
||||
needsMigration: db.getDbStatus().exists && !db.hasPasswordColumn(),
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/profile/login", async (req, res) => {
|
||||
const pseudo = (req.body.pseudo || "").trim();
|
||||
const password = req.body.password || "";
|
||||
|
||||
if (!pseudo || !password) {
|
||||
return res.status(400).render("profile/login", {
|
||||
page: "profile",
|
||||
title: "Connexion",
|
||||
error: "Pseudo et mot de passe requis.",
|
||||
pseudo,
|
||||
needsMigration: db.getDbStatus().exists && !db.hasPasswordColumn(),
|
||||
});
|
||||
}
|
||||
|
||||
if (!db.getDbStatus().exists) {
|
||||
return res.status(503).render("profile/login", {
|
||||
page: "profile",
|
||||
title: "Connexion",
|
||||
error: "Base de données indisponible.",
|
||||
pseudo,
|
||||
needsMigration: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (!db.hasPasswordColumn()) {
|
||||
return res.status(503).render("profile/login", {
|
||||
page: "profile",
|
||||
title: "Connexion",
|
||||
error: "Migration web manquante. Redémarrez le serveur Hytale pour mettre à jour la base.",
|
||||
pseudo,
|
||||
needsMigration: true,
|
||||
});
|
||||
}
|
||||
|
||||
const record = db.getPlayerAuthByDisplayName(pseudo);
|
||||
if (!record) {
|
||||
return res.status(401).render("profile/login", {
|
||||
page: "profile",
|
||||
title: "Connexion",
|
||||
error: "Pseudo introuvable. Connectez-vous d'abord au serveur Hytale pour créer votre profil.",
|
||||
pseudo,
|
||||
needsMigration: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (!record.password_hash) {
|
||||
return res.status(401).render("profile/login", {
|
||||
page: "profile",
|
||||
title: "Connexion",
|
||||
error: "Aucun mot de passe défini. Utilisez /mmorpg password <mot_de_passe> en jeu.",
|
||||
pseudo,
|
||||
needsMigration: false,
|
||||
});
|
||||
}
|
||||
|
||||
const valid = await auth.verifyPassword(password, record.password_hash);
|
||||
if (!valid) {
|
||||
return res.status(401).render("profile/login", {
|
||||
page: "profile",
|
||||
title: "Connexion",
|
||||
error: "Mot de passe incorrect.",
|
||||
pseudo,
|
||||
needsMigration: false,
|
||||
});
|
||||
}
|
||||
|
||||
req.session.playerUuid = record.uuid;
|
||||
req.session.displayName = record.display_name;
|
||||
res.redirect("/profile");
|
||||
});
|
||||
|
||||
app.post("/profile/logout", requireLogin, (req, res) => {
|
||||
req.session.destroy(() => {
|
||||
res.redirect("/profile");
|
||||
});
|
||||
});
|
||||
|
||||
app.use((req, res) => {
|
||||
res.status(404).render("error", {
|
||||
page: "",
|
||||
title: "Page introuvable",
|
||||
message: "La page demandée n'existe pas.",
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
const status = db.getDbStatus();
|
||||
console.log(`Orion.ovh Web → http://localhost:${PORT}`);
|
||||
if (!status.exists) {
|
||||
console.warn(`Base SQLite introuvable : ${status.path}`);
|
||||
console.warn("Lancez le serveur Hytale pour créer mmorpg.db à la racine du monorepo.");
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user