mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
feat(reader): merge reader settings field by field over PATCH
PUT reader/defaults/:formatGroup demands the full settings object, so a client has to send every field of a group to change one of them. That forces a client to own fields it does not model and to echo back values it never shows, and it turns two clients editing different fields into a last-writer-wins race over the whole row. Add PATCH for both defaults and per-book preferences. The defaults patch merges over the stored row with the built-in defaults folded in underneath, so the row stays a complete object for the web client's sanitiser while a partial caller leaves no holes. The per-book patch takes set and unset and resolves both in one statement, so a concurrent patch cannot lose a field to a read-modify-write, and deletes the row once its last key goes, which is the same state as never having been customized. Nulls are never stripped: a null fontFamily means "use the book's own font", not an absent key. Keep only the keys the caller actually sent. Zod applies .default() while parsing a partial, so a cbx patch that never mentioned spreadGap came back carrying one; the existing per-book PUT had the same latent bug and now shares the fix, which stops a touched comic from being pinned to the default gap. iOS is the only caller of the new routes. It keeps the layout fields on the device and syncs the look fields, which is exactly the split a partial write makes expressible. The web client does not call them and still PUTs full snapshots.
This commit is contained in:
@@ -196,6 +196,34 @@ export const AUDIO_READER_DEFAULTS: AudioReaderSettings = {
|
||||
skipForwardSeconds: 30,
|
||||
};
|
||||
|
||||
/**
|
||||
* Body of `PATCH /reader/defaults/:formatGroup`.
|
||||
*
|
||||
* Only the keys present in `set` are written. Every other key of the group keeps whatever the
|
||||
* stored row already holds, so a client that owns a subset of the fields can save its own without
|
||||
* having to send, and therefore without having to know, the rest. The iOS app is the only caller:
|
||||
* it keeps the layout fields on the device and sends only the look fields.
|
||||
*/
|
||||
export interface ReaderDefaultsPatchBody<
|
||||
G extends ReaderFormatGroup = ReaderFormatGroup,
|
||||
> {
|
||||
set: Partial<ReaderSettingsMap[G]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of `PATCH /reader/preferences/:bookFileId`.
|
||||
*
|
||||
* `set` pins fields on the book; `unset` removes them so the field falls back to the account
|
||||
* default. At least one of the two must be non-empty, and a key may not appear in both. A row left
|
||||
* with no keys is deleted, which is the same state as never having been customized.
|
||||
*/
|
||||
export interface ReaderPreferencePatchBody<
|
||||
G extends ReaderFormatGroup = ReaderFormatGroup,
|
||||
> {
|
||||
set?: Partial<ReaderSettingsMap[G]>;
|
||||
unset?: string[];
|
||||
}
|
||||
|
||||
export const READER_GROUP_DEFAULTS: ReaderSettingsMap = {
|
||||
epub: EPUB_READER_DEFAULTS,
|
||||
pdf: PDF_READER_DEFAULTS,
|
||||
|
||||
Reference in New Issue
Block a user