Generalize podcast-specific UI for study-only users
Other people using this app just want to do personal Bible study, not produce a podcast episode, so the always-visible chrome (chunk metadata, DOCX import, exports) now says "Session" instead of "Episode" and marks podcast-only fields as optional. The "Prepare for Podcast" prompt no longer hardcodes "Verse by Verse with Nate" for every user — it now pulls from a new "Podcast / show name" field in Account Settings, so it still works exactly as before once you set yours, but produces a sensible generic prompt for anyone who hasn't. Also added an explanatory blurb to the session-list import page and loosened its docx parser to accept "Session N — Title" in addition to "Ep. N — Title". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+12
-1
@@ -58,6 +58,11 @@ export function initDb() {
|
||||
`);
|
||||
}
|
||||
|
||||
// Older databases predate the podcast-name profile setting.
|
||||
if (!userCols.some((c) => c.name === 'podcast_name')) {
|
||||
db.exec('ALTER TABLE users ADD COLUMN podcast_name TEXT');
|
||||
}
|
||||
|
||||
console.log(`SQLite database ready at ${DB_PATH}`);
|
||||
}
|
||||
|
||||
@@ -98,7 +103,8 @@ function parseUserRow(row) {
|
||||
|
||||
const USER_SELECT = `
|
||||
SELECT id, email, password_hash AS passwordHash, created_at AS createdAt,
|
||||
totp_secret AS totpSecret, totp_enabled AS totpEnabled, backup_codes AS backupCodesRaw
|
||||
totp_secret AS totpSecret, totp_enabled AS totpEnabled, backup_codes AS backupCodesRaw,
|
||||
podcast_name AS podcastName
|
||||
FROM users
|
||||
`;
|
||||
|
||||
@@ -124,6 +130,11 @@ export function disableTotp(userId) {
|
||||
`).run(userId);
|
||||
}
|
||||
|
||||
/** Sets or clears the show name used in the "Prepare for Podcast" prompt. */
|
||||
export function setPodcastName(userId, podcastName) {
|
||||
db.prepare('UPDATE users SET podcast_name = ? WHERE id = ?').run(podcastName || null, userId);
|
||||
}
|
||||
|
||||
/** Rewrites the remaining backup-code hashes after one is used (single-use codes). */
|
||||
export function setBackupCodeHashes(userId, backupCodeHashes) {
|
||||
db.prepare('UPDATE users SET backup_codes = ? WHERE id = ?').run(JSON.stringify(backupCodeHashes), userId);
|
||||
|
||||
+19
-5
@@ -7,7 +7,7 @@ import { dirname, join } from 'path';
|
||||
import {
|
||||
initDb, getAllProjects, getProject, upsertProject, deleteProject,
|
||||
countUsers, createUser, getUserByEmail, getUserById, claimOrphanProjects,
|
||||
enableTotp, disableTotp, setBackupCodeHashes,
|
||||
enableTotp, disableTotp, setBackupCodeHashes, setPodcastName,
|
||||
} from './db.js';
|
||||
import { SqliteSessionStore } from './sessionStore.js';
|
||||
import {
|
||||
@@ -80,7 +80,7 @@ app.post('/api/auth/register', async (req, res) => {
|
||||
req.session.regenerate((err) => {
|
||||
if (err) return res.status(500).json({ error: 'Could not create session.' });
|
||||
req.session.userId = user.id;
|
||||
res.json({ id: user.id, email: user.email, totpEnabled: false });
|
||||
res.json({ id: user.id, email: user.email, totpEnabled: false, podcastName: null });
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('POST /api/auth/register error:', err);
|
||||
@@ -108,7 +108,7 @@ app.post('/api/auth/login', async (req, res) => {
|
||||
return res.json({ mfaRequired: true });
|
||||
}
|
||||
req.session.userId = user.id;
|
||||
res.json({ id: user.id, email: user.email, totpEnabled: false });
|
||||
res.json({ id: user.id, email: user.email, totpEnabled: false, podcastName: user.podcastName ?? null });
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('POST /api/auth/login error:', err);
|
||||
@@ -146,7 +146,7 @@ app.post('/api/auth/mfa/verify', async (req, res) => {
|
||||
req.session.regenerate((err) => {
|
||||
if (err) return res.status(500).json({ error: 'Could not create session.' });
|
||||
req.session.userId = user.id;
|
||||
res.json({ id: user.id, email: user.email, totpEnabled: true });
|
||||
res.json({ id: user.id, email: user.email, totpEnabled: true, podcastName: user.podcastName ?? null });
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('POST /api/auth/mfa/verify error:', err);
|
||||
@@ -164,7 +164,21 @@ app.post('/api/auth/logout', (req, res) => {
|
||||
app.get('/api/auth/me', (req, res) => {
|
||||
const user = req.session?.userId ? getUserById(req.session.userId) : null;
|
||||
if (!user) return res.status(401).json({ error: 'Not signed in.' });
|
||||
res.json({ id: user.id, email: user.email, totpEnabled: user.totpEnabled });
|
||||
res.json({ id: user.id, email: user.email, totpEnabled: user.totpEnabled, podcastName: user.podcastName ?? null });
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PATCH /api/auth/profile — update account-level settings (currently just podcastName)
|
||||
// ---------------------------------------------------------------------------
|
||||
app.patch('/api/auth/profile', requireAuth, (req, res) => {
|
||||
try {
|
||||
const podcastName = String(req.body?.podcastName ?? '').trim().slice(0, 200);
|
||||
setPodcastName(req.session.userId, podcastName || null);
|
||||
res.json({ podcastName: podcastName || null });
|
||||
} catch (err) {
|
||||
console.error('PATCH /api/auth/profile error:', err);
|
||||
res.status(500).json({ error: 'Failed to save profile.' });
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user