Player text visibility option (#55)

* feat: Add player admin UI settings

Co-authored-by: mathieumoran1 <[email protected]>

* Refactor player UI settings to combine source and progress

Co-authored-by: mathieumoran1 <[email protected]>

* Refactor admin settings to use accordion layout

Co-authored-by: mathieumoran1 <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
This commit was merged in pull request #55.
This commit is contained in:
Mathieu Moran
2025-12-22 16:17:31 -05:00
committed by GitHub
co-authored by Cursor Agent
parent 57918f8b88
commit 59eb70dea9
4 changed files with 273 additions and 46 deletions
+53
View File
@@ -13,6 +13,7 @@ const COMPLETION_MODAL_KEY = "completion_modal_content";
const WINNERS_BY_YEAR_KEY = "winners_by_year";
const OSCAR_DATES_BY_YEAR_KEY = "oscar_date_by_year";
const APP_VERSION_KEY = "app_version_control";
const PLAYER_ADMIN_STATUS_UI_KEY = "player_admin_status_ui";
const crypto = require("crypto");
@@ -23,6 +24,12 @@ const DEFAULT_COMPLETION_MODAL = Object.freeze({
bodyHtml: "",
});
const DEFAULT_PLAYER_ADMIN_STATUS_UI = Object.freeze({
// Controls the admin-only debug/status block in player.html
showSource: true,
showProgress: true,
});
function parseOscarYear(raw) {
if (raw === undefined || raw === null || raw === "") return null;
const n = Number(raw);
@@ -146,6 +153,13 @@ function normalizeCompletionModal(rawValue) {
};
}
function normalizePlayerAdminStatusUi(rawValue) {
const v = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue) ? rawValue : {};
const showSource = v.showSource === undefined ? DEFAULT_PLAYER_ADMIN_STATUS_UI.showSource : !!v.showSource;
const showProgress = v.showProgress === undefined ? DEFAULT_PLAYER_ADMIN_STATUS_UI.showProgress : !!v.showProgress;
return { showSource, showProgress };
}
function createId() {
try {
// Node 16+ / 18+: randomUUID
@@ -431,6 +445,45 @@ router.get("/app-version", async (req, res) => {
}
});
// Public: get player admin status UI flags (source/progress visibility)
router.get("/player-admin-status-ui", async (req, res) => {
try {
const existing = await Setting.findOne({ key: PLAYER_ADMIN_STATUS_UI_KEY }).select("value");
const normalized = normalizePlayerAdminStatusUi(existing?.value);
return res.status(200).json(normalized);
} catch (_) {
return res.status(200).json(DEFAULT_PLAYER_ADMIN_STATUS_UI);
}
});
// Admin-only: update player admin status UI flags
// Body: { showSource?: boolean, showProgress?: boolean }
router.put("/player-admin-status-ui", async (req, res) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ message: "Authentication token is required" });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (!decoded.admin) {
return res.status(403).json({ message: "You do not have admin privileges" });
}
const normalized = normalizePlayerAdminStatusUi(req.body);
const updated = await Setting.findOneAndUpdate(
{ key: PLAYER_ADMIN_STATUS_UI_KEY },
{ $set: { value: normalized } },
{ upsert: true, new: true }
).select("value");
return res.status(200).json(normalizePlayerAdminStatusUi(updated?.value));
} catch (err) {
if (err instanceof jwt.JsonWebTokenError) {
return res.status(401).json({ message: "Invalid or expired token" });
}
return res.status(500).json({ error: err.message });
}
});
// Admin-only: create a new version entry (optionally activates if none or if activate=true)
// Body: { version: "1.2.3", message?: "..." }
router.post("/app-version", async (req, res) => {