Implement a large number of changes, including overhauled styling and a settings page

This commit is contained in:
2026-03-24 11:16:22 +01:00
parent 44efc7818a
commit 169650c71b
8 changed files with 2633 additions and 1792 deletions
+165 -5
View File
@@ -298,6 +298,8 @@ db.prepare(`
const insertUserStmt = db.prepare("INSERT INTO users (username, password) VALUES (?, ?)");
const getUserStmt = db.prepare("SELECT username, password FROM users WHERE username = ?");
const updateUsernameStmt = db.prepare("UPDATE users SET username = ? WHERE username = ?");
const updatePasswordStmt = db.prepare("UPDATE users SET password = ? WHERE username = ?");
const getLegacyChatsStmt = db.prepare(`
SELECT role, content
FROM chats
@@ -529,6 +531,71 @@ const listPublishedPersonaIdsStmt = db.prepare(`
WHERE creator_username = ?
`);
const getChatMessageCountForUserStmt = db.prepare(`
SELECT COUNT(*) as count
FROM chat_messages cm
JOIN chat_sessions cs ON cs.id = cm.chat_id
WHERE cs.username = ?
`);
const getPersonaCountForUserStmt = db.prepare(`
SELECT COUNT(*) as count
FROM personas
WHERE username = ?
`);
const getPublishedPersonaCountForUserStmt = db.prepare(`
SELECT COUNT(*) as count
FROM persona_market
WHERE creator_username = ?
`);
const updateLegacyChatsUsernameStmt = db.prepare(`
UPDATE chats
SET username = ?
WHERE username = ?
`);
const updateChatSessionsUsernameStmt = db.prepare(`
UPDATE chat_sessions
SET username = ?
WHERE username = ?
`);
const updatePersonasUsernameStmt = db.prepare(`
UPDATE personas
SET username = ?
WHERE username = ?
`);
const updatePersonasSourceCreatorStmt = db.prepare(`
UPDATE personas
SET source_creator_username = ?
WHERE source_creator_username = ?
`);
const updateUserSettingsUsernameStmt = db.prepare(`
UPDATE user_settings
SET username = ?
WHERE username = ?
`);
const updatePersonaMarketCreatorStmt = db.prepare(`
UPDATE persona_market
SET creator_username = ?
WHERE creator_username = ?
`);
const renameUserTransaction = db.transaction((currentUsername, nextUsername) => {
updateUsernameStmt.run(nextUsername, currentUsername);
updateLegacyChatsUsernameStmt.run(nextUsername, currentUsername);
updateChatSessionsUsernameStmt.run(nextUsername, currentUsername);
updatePersonasUsernameStmt.run(nextUsername, currentUsername);
updatePersonasSourceCreatorStmt.run(nextUsername, currentUsername);
updateUserSettingsUsernameStmt.run(nextUsername, currentUsername);
updatePersonaMarketCreatorStmt.run(nextUsername, currentUsername);
});
const deleteMarketPersonaByPersonaIdStmt = db.prepare(`
DELETE FROM persona_market
WHERE persona_id = ?
@@ -640,6 +707,76 @@ app.post("/logout", (req, res) => {
req.session.destroy(() => res.json({message: "Logged out"}));
});
const requireLogin = (req, res, next) => {
if (!req.session.user) return res.status(403).json({error: "Not logged in"});
next();
};
app.get("/settings/profile", requireLogin, (req, res) => {
const user = getUserStmt.get(req.session.user);
if (!user) return res.status(404).json({error: "User not found"});
res.json({username: user.username});
});
app.put("/settings/username", requireLogin, async (req, res) => {
const currentUsername = req.session.user;
const nextUsername = (req.body?.username || "").trim();
const password = req.body?.password || "";
if (!nextUsername) {
return res.status(400).json({error: "New username is required"});
}
if (nextUsername.length < 3) {
return res.status(400).json({error: "Username must be at least 3 characters"});
}
if (nextUsername === currentUsername) {
return res.status(400).json({error: "That is already your username"});
}
const currentUser = getUserStmt.get(currentUsername);
if (!currentUser) {
return res.status(404).json({error: "User not found"});
}
const valid = await bcrypt.compare(password, currentUser.password);
if (!valid) {
return res.status(403).json({error: "Current password is incorrect"});
}
if (getUserStmt.get(nextUsername)) {
return res.status(400).json({error: "Username already exists"});
}
renameUserTransaction(currentUsername, nextUsername);
req.session.user = nextUsername;
res.json({username: nextUsername});
});
app.put("/settings/password", requireLogin, async (req, res) => {
const username = req.session.user;
const currentPassword = req.body?.currentPassword || "";
const nextPassword = req.body?.newPassword || "";
if (!currentPassword || !nextPassword) {
return res.status(400).json({error: "Current and new password are required"});
}
if (nextPassword.length < 6) {
return res.status(400).json({error: "New password must be at least 6 characters"});
}
const user = getUserStmt.get(username);
if (!user) {
return res.status(404).json({error: "User not found"});
}
const valid = await bcrypt.compare(currentPassword, user.password);
if (!valid) {
return res.status(403).json({error: "Current password is incorrect"});
}
const hashed = await bcrypt.hash(nextPassword, 10);
updatePasswordStmt.run(hashed, username);
res.json({message: "Password updated"});
});
const normalizePersonaField = (value) => {
const trimmed = (value || "").trim();
return trimmed ? trimmed : null;
@@ -683,11 +820,6 @@ const buildUserPersonaPrompt = (persona) => {
return lines.join("\n");
};
const requireLogin = (req, res, next) => {
if (!req.session.user) return res.status(403).json({error: "Not logged in"});
next();
};
app.get("/models", async (req, res) => {
try {
if (modelsCache && (Date.now() - modelsCacheAt) < MODELS_TTL_MS) {
@@ -898,6 +1030,26 @@ app.get("/personas/market", requireLogin, (req, res) => {
res.json({personas});
});
app.get("/dashboard/summary", requireLogin, (req, res) => {
const user = req.session.user;
const chats = countChatSessionsStmt.get(user).count;
const messages = getChatMessageCountForUserStmt.get(user).count;
const personas = getPersonaCountForUserStmt.get(user).count;
const published = getPublishedPersonaCountForUserStmt.get(user).count;
const marketPersonas = listMarketPersonasStmt.all().length;
res.json({
username: user,
stats: {
chats,
messages,
personas,
published,
marketPersonas
}
});
});
app.post("/personas/:id/publish", requireLogin, (req, res) => {
const user = req.session.user;
const personaId = Number(req.params.id);
@@ -1090,6 +1242,14 @@ app.get("/chat/history", requireLogin, (req, res) => {
res.json({history: chats});
});
app.get("/market", (req, res) => {
res.sendFile(path.resolve("public/market.html"));
});
app.get("/settings", (req, res) => {
res.sendFile(path.resolve("public/settings.html"));
});
app.get("/", (req, res) => {
res.sendFile(path.resolve("public/index.html"));
});