Sync bookmarks, highlights and display preferences to the account
Build and Push Docker Image / build-and-push (push) Failing after 19s
Build and Push Docker Image / build-and-push (push) Failing after 19s
Bookmarks and text highlights are saved per user and merged across devices (union on first contact, newer server copy when this device is clean). Theme, default translation, verse layout, commentary source and reading position sync too, each with its own timestamp so the newest change to each setting wins. The Reader never moves under you while it is open. Signing out clears this device's marks and forgets sync timestamps. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
297f2c7e84
commit
760c1e568b
20 files changed
+976
-18
No files matched your search
+47
-1
@@ -12,7 +12,7 @@ import {
|
||||
getShareToken, setShareToken, clearShareToken, getProjectByShareToken,
|
||||
adminGetAllUsers, adminDeleteUser, adminGetAllProjects, adminGetProject, adminDeleteProject,
|
||||
setUserPassword, destroyAllSessionsForUser,
|
||||
getAllReaderInk, setReaderInkPage,
|
||||
getAllReaderInk, setReaderInkPage, getReaderMarks, setReaderMarks, getUserPrefs, setUserPrefs,
|
||||
getReadingProgress, setReadingProgress,
|
||||
setReminderSettings,
|
||||
} from './db.js';
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
} from './auth.js';
|
||||
import { startReminderScheduler, sendTestReminder, isValidTimeZone } from './reminders.js';
|
||||
import { registerBibleRoutes } from './bibleApi.js';
|
||||
import { cleanMarks } from './readerMarks.js';
|
||||
import { cleanPrefsPatch, mergePrefs } from './prefs.js';
|
||||
|
||||
// Shape returned to the client for the signed-in user (login, MFA verify, /auth/me).
|
||||
function userPayload(user) {
|
||||
@@ -448,6 +450,50 @@ app.put('/api/reader/ink/:book/:chapter', requireAuth, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/prefs — the user's synced display preferences ({ prefs: { key: { v, at } } })
|
||||
app.get('/api/prefs', requireAuth, (req, res) => {
|
||||
try {
|
||||
res.json({ prefs: getUserPrefs(req.session.userId) });
|
||||
} catch (err) {
|
||||
console.error('GET /api/prefs error:', err);
|
||||
res.status(500).json({ error: 'Failed to load preferences.' });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/prefs — merge changed preferences in (newest change per key wins); returns the result
|
||||
app.put('/api/prefs', requireAuth, (req, res) => {
|
||||
try {
|
||||
const merged = mergePrefs(getUserPrefs(req.session.userId), cleanPrefsPatch(req.body));
|
||||
setUserPrefs(req.session.userId, merged);
|
||||
res.json({ prefs: merged });
|
||||
} catch (err) {
|
||||
console.error('PUT /api/prefs error:', err);
|
||||
res.status(500).json({ error: 'Failed to save preferences.' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/reader/marks — the user's bookmarks + highlights, with an updatedAt to compare against
|
||||
app.get('/api/reader/marks', requireAuth, (req, res) => {
|
||||
try {
|
||||
res.json(getReaderMarks(req.session.userId));
|
||||
} catch (err) {
|
||||
console.error('GET /api/reader/marks error:', err);
|
||||
res.status(500).json({ error: 'Failed to load bookmarks and highlights.' });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/reader/marks — replace the user's bookmarks + highlights ({ bookmarks, highlights })
|
||||
app.put('/api/reader/marks', requireAuth, (req, res) => {
|
||||
try {
|
||||
const marks = cleanMarks(req.body);
|
||||
if (!marks) return res.status(400).json({ error: 'Body must be an object.' });
|
||||
res.json({ ok: true, updatedAt: setReaderMarks(req.session.userId, marks) });
|
||||
} catch (err) {
|
||||
console.error('PUT /api/reader/marks error:', err);
|
||||
res.status(500).json({ error: 'Failed to save bookmarks and highlights.' });
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reading plan progress — cross-device sync for started/completed reading plans
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user