forked from akai/readest
72233e1c6a
* docs(sync): design spec for syncing reading status (#4634) Field-level LWW for reading_status (dedicated reading_status_updated_at), a new 'abandoned' status in the Readest UI, and a koplugin bridge to KOReader's native summary.status (whole-library apply + capture). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sync): implementation plan for syncing reading status (#4634) Bite-sized TDD tasks across 3 parts: A) cloud field-level LWW (reading_status_updated_at on server upsert + client pull-merge), B) 'abandoned'/On-hold status in the Readest UI, C) koplugin bridge to KOReader summary.status (mapping + reconcile + whole-library apply/capture). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sync): add reading_status_updated_at for field-level status LWW (#4634) * feat(sync): stamp readingStatusUpdatedAt on status change in updateBookProgress Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sync): stamp status timestamp on explicit library status edits * feat(sync): resolve reading status by its own timestamp in client pull-merge * feat(sync): resolve reading status by its own timestamp in server upsert (#4634) * fix(sync): tighten reading-status merge typing + strengthen test (A5 review) Replace as-unknown-as double-casts at read sites with typed locals (clientBook/serverBook); retain a single as-unknown-as only at the server-wins construction site where the static type is too narrow. Strengthen test 3 to assert both fields with toEqual. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(library): render the 'On hold' (abandoned) status badge * feat(library): add 'Mark as On hold' actions + i18n for abandoned status Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * i18n: translate 'On hold' and 'Mark as On hold' for the abandoned status (#4634) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(koplugin): add reading-status mapping + reconcile between Readest and KOReader * feat(koplugin): persist + sync reading_status_updated_at in LibraryStore Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(koplugin): bridge reading status to KOReader summary.status on library sync (#4634) * test(sync): cover koplugin v1->v2 migration + tighten status-sync tests (final review) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sync): redesign KOReader first-sync (decisive-only + bootstrap) (#4634) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(koplugin): safe first-sync of reading status (decisive-only + bootstrap) (#4634) KOReader auto-sets summary.status='reading' on first open, and legacy Readest statuses have reading_status_updated_at=0, so pure timestamp LWW let opening a finished book downgrade it. Restrict sync to deliberate statuses (finished/ complete, abandoned/on-hold, unread->clear); never capture KO 'reading'/'New'. On the unsynced baseline (Readest ts=0) conflicts resolve Readest-authoritative, then stamp now_ms to exit bootstrap into steady-state LWW. reconcile now returns write_ko/write_store flags; statussync captures now_ms once and equalizes both sides (convergent, idempotent, resumable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(koplugin): cover remaining first-sync graph cells + document sort effect (review) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
112 lines
5.0 KiB
SQL
112 lines
5.0 KiB
SQL
ALTER FUNCTION auth.uid() OWNER TO supabase_auth_admin;
|
|
ALTER FUNCTION auth.role() OWNER TO supabase_auth_admin;
|
|
|
|
CREATE TABLE public.books (
|
|
user_id uuid NOT NULL,
|
|
book_hash text NOT NULL,
|
|
meta_hash text NULL,
|
|
format text NULL,
|
|
title text NULL,
|
|
source_title text NULL,
|
|
author text NULL,
|
|
"group" text NULL,
|
|
tags text[] NULL,
|
|
created_at timestamp with time zone NULL DEFAULT now(),
|
|
updated_at timestamp with time zone NULL DEFAULT now(),
|
|
deleted_at timestamp with time zone NULL,
|
|
uploaded_at timestamp with time zone NULL,
|
|
progress integer[] NULL,
|
|
reading_status text NULL,
|
|
reading_status_updated_at timestamp with time zone NULL,
|
|
group_id text NULL,
|
|
group_name text NULL,
|
|
metadata json NULL,
|
|
CONSTRAINT books_pkey PRIMARY KEY (user_id, book_hash),
|
|
CONSTRAINT books_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
|
|
);
|
|
|
|
ALTER TABLE public.books ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY select_books ON public.books FOR SELECT TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY insert_books ON public.books FOR INSERT TO authenticated WITH CHECK ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY update_books ON public.books FOR UPDATE TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY delete_books ON public.books FOR DELETE TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
|
|
CREATE TABLE public.book_configs (
|
|
user_id uuid NOT NULL,
|
|
book_hash text NOT NULL,
|
|
meta_hash text NULL,
|
|
location text NULL,
|
|
xpointer text NULL,
|
|
progress jsonb NULL,
|
|
rsvp_position text NULL,
|
|
search_config jsonb NULL,
|
|
view_settings jsonb NULL,
|
|
created_at timestamp with time zone NULL DEFAULT now(),
|
|
updated_at timestamp with time zone NULL DEFAULT now(),
|
|
deleted_at timestamp with time zone NULL,
|
|
CONSTRAINT book_configs_pkey PRIMARY KEY (user_id, book_hash),
|
|
CONSTRAINT book_configs_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
|
|
);
|
|
|
|
ALTER TABLE public.book_configs ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY select_book_configs ON public.book_configs FOR SELECT TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY insert_book_configs ON public.book_configs FOR INSERT TO authenticated WITH CHECK ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY update_book_configs ON public.book_configs FOR UPDATE TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY delete_book_configs ON public.book_configs FOR DELETE TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
|
|
CREATE TABLE public.book_notes (
|
|
user_id uuid NOT NULL,
|
|
book_hash text NOT NULL,
|
|
meta_hash text NULL,
|
|
id text NOT NULL,
|
|
type text NULL,
|
|
cfi text NULL,
|
|
xpointer0 text NULL,
|
|
xpointer1 text NULL,
|
|
text text NULL,
|
|
style text NULL,
|
|
color text NULL,
|
|
note text NULL,
|
|
page integer NULL,
|
|
global boolean NULL,
|
|
created_at timestamp with time zone NULL DEFAULT now(),
|
|
updated_at timestamp with time zone NULL DEFAULT now(),
|
|
deleted_at timestamp with time zone NULL,
|
|
CONSTRAINT book_notes_pkey PRIMARY KEY (user_id, book_hash, id),
|
|
CONSTRAINT book_notes_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
|
|
);
|
|
|
|
ALTER TABLE public.book_notes ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY select_book_notes ON public.book_notes FOR SELECT TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY insert_book_notes ON public.book_notes FOR INSERT TO authenticated WITH CHECK ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY update_book_notes ON public.book_notes FOR UPDATE TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
CREATE POLICY delete_book_notes ON public.book_notes FOR DELETE TO authenticated USING ((SELECT auth.uid()) = user_id);
|
|
|
|
CREATE TABLE public.files (
|
|
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
user_id uuid NOT NULL,
|
|
book_hash text NULL,
|
|
file_key text NOT NULL,
|
|
file_size bigint NOT NULL,
|
|
created_at timestamp with time zone NULL DEFAULT now(),
|
|
updated_at timestamp with time zone NULL DEFAULT now(),
|
|
deleted_at timestamp with time zone NULL,
|
|
CONSTRAINT files_pkey PRIMARY KEY (id),
|
|
CONSTRAINT files_file_key_key UNIQUE (file_key),
|
|
CONSTRAINT files_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX idx_files_user_id_deleted_at ON public.files (user_id, deleted_at);
|
|
CREATE INDEX idx_files_file_key ON public.files (file_key);
|
|
CREATE INDEX idx_files_file_key_deleted_at ON public.files (file_key, deleted_at);
|
|
|
|
ALTER TABLE public.files ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY files_insert ON public.files FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY files_select ON public.files FOR SELECT USING (auth.uid() = user_id AND deleted_at IS NULL);
|
|
CREATE POLICY files_update ON public.files FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (deleted_at IS NULL OR deleted_at > now());
|
|
CREATE POLICY files_delete ON public.files FOR DELETE USING (auth.uid() = user_id);
|
|
|
|
GRANT ALL ON public.books TO authenticated;
|
|
GRANT ALL ON public.book_configs TO authenticated;
|
|
GRANT ALL ON public.book_notes TO authenticated;
|
|
GRANT ALL ON public.files TO authenticated; |