feat(bookmarks): api+store — per-user CRUD, owner-scoped 404, percent-ordered list
This commit is contained in:
@@ -19,3 +19,11 @@ CREATE TABLE IF NOT EXISTS reading_progress (
|
||||
locator JSONB NOT NULL DEFAULT '{}', percent DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, library_id, book_path));
|
||||
CREATE TABLE IF NOT EXISTS bookmarks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
library_id BIGINT NOT NULL, book_path TEXT NOT NULL,
|
||||
locator JSONB NOT NULL, percent DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now());
|
||||
CREATE INDEX IF NOT EXISTS bookmarks_user_book_idx ON bookmarks (user_id, library_id, book_path);
|
||||
|
||||
@@ -314,6 +314,57 @@ func (s *Store) ListProgress(ctx context.Context, userID int64) ([]Progress, err
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ---------- bookmarks ----------
|
||||
|
||||
type Bookmark struct {
|
||||
ID int64
|
||||
LibraryID int64
|
||||
BookPath string
|
||||
Locator []byte
|
||||
Percent float64
|
||||
Note string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (s *Store) InsertBookmark(ctx context.Context, userID, libID int64, bookPath string, locator []byte, percent float64, note string) (int64, error) {
|
||||
var id int64
|
||||
err := s.P.QueryRow(ctx,
|
||||
`INSERT INTO bookmarks (user_id, library_id, book_path, locator, percent, note)
|
||||
VALUES ($1,$2,$3,$4,$5,$6) RETURNING id`,
|
||||
userID, libID, bookPath, locator, percent, note).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) ListBookmarks(ctx context.Context, userID, libID int64, bookPath string) ([]Bookmark, error) {
|
||||
rows, err := s.P.Query(ctx,
|
||||
`SELECT id, library_id, book_path, locator, percent, note, created_at
|
||||
FROM bookmarks WHERE user_id=$1 AND library_id=$2 AND book_path=$3
|
||||
ORDER BY percent ASC, id ASC`, userID, libID, bookPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []Bookmark
|
||||
for rows.Next() {
|
||||
var b Bookmark
|
||||
if err := rows.Scan(&b.ID, &b.LibraryID, &b.BookPath, &b.Locator, &b.Percent, &b.Note, &b.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, b)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) UpdateBookmarkNote(ctx context.Context, userID, id int64, note string) (bool, error) {
|
||||
res, err := s.P.Exec(ctx, `UPDATE bookmarks SET note=$3 WHERE id=$1 AND user_id=$2`, id, userID, note)
|
||||
return res.RowsAffected() > 0, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteBookmark(ctx context.Context, userID, id int64) (bool, error) {
|
||||
res, err := s.P.Exec(ctx, `DELETE FROM bookmarks WHERE id=$1 AND user_id=$2`, id, userID)
|
||||
return res.RowsAffected() > 0, err
|
||||
}
|
||||
|
||||
func (s *Store) GetProgress(ctx context.Context, userID, libID int64, bookPath string) (Progress, error) {
|
||||
var pr Progress
|
||||
err := s.P.QueryRow(ctx,
|
||||
|
||||
Reference in New Issue
Block a user