Add GetNotebook RPC for pulling complete notebook data

New RPC returns notebook metadata, all pages, and all strokes for
a given server-side notebook ID. Enables desktop and other clients
to download notebooks from the server (pull sync).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 15:06:20 -07:00
parent 651eabe995
commit f1b67b9909
4 changed files with 340 additions and 78 deletions

View File

@@ -114,6 +114,71 @@ func (s *SyncService) SyncNotebook(ctx context.Context, req *pb.SyncNotebookRequ
}, nil
}
func (s *SyncService) GetNotebook(ctx context.Context, req *pb.GetNotebookRequest) (*pb.GetNotebookResponse, error) {
userID, ok := UserIDFromContext(ctx)
if !ok {
return nil, status.Error(codes.Internal, "missing user context")
}
var resp pb.GetNotebookResponse
var syncedAt int64
err := s.DB.QueryRowContext(ctx,
"SELECT id, remote_id, title, page_size, synced_at FROM notebooks WHERE id = ? AND user_id = ?",
req.NotebookId, userID,
).Scan(&resp.ServerNotebookId, &resp.RemoteId, &resp.Title, &resp.PageSize, &syncedAt)
if err == sql.ErrNoRows {
return nil, status.Error(codes.NotFound, "notebook not found")
}
if err != nil {
return nil, status.Errorf(codes.Internal, "query notebook: %v", err)
}
resp.SyncedAt = timestamppb.New(time.UnixMilli(syncedAt))
pageRows, err := s.DB.QueryContext(ctx,
"SELECT id, remote_id, page_number FROM pages WHERE notebook_id = ? ORDER BY page_number",
resp.ServerNotebookId,
)
if err != nil {
return nil, status.Errorf(codes.Internal, "query pages: %v", err)
}
defer func() { _ = pageRows.Close() }()
for pageRows.Next() {
var pageID, remoteID int64
var pageNum int32
if err := pageRows.Scan(&pageID, &remoteID, &pageNum); err != nil {
return nil, status.Errorf(codes.Internal, "scan page: %v", err)
}
pd := &pb.PageData{
PageId: remoteID,
PageNumber: pageNum,
}
strokeRows, err := s.DB.QueryContext(ctx,
"SELECT pen_size, color, style, point_data, stroke_order FROM strokes WHERE page_id = ? ORDER BY stroke_order",
pageID,
)
if err != nil {
return nil, status.Errorf(codes.Internal, "query strokes: %v", err)
}
for strokeRows.Next() {
var sd pb.StrokeData
if err := strokeRows.Scan(&sd.PenSize, &sd.Color, &sd.Style, &sd.PointData, &sd.StrokeOrder); err != nil {
_ = strokeRows.Close()
return nil, status.Errorf(codes.Internal, "scan stroke: %v", err)
}
pd.Strokes = append(pd.Strokes, &sd)
}
_ = strokeRows.Close()
resp.Pages = append(resp.Pages, pd)
}
return &resp, nil
}
func (s *SyncService) DeleteNotebook(ctx context.Context, req *pb.DeleteNotebookRequest) (*pb.DeleteNotebookResponse, error) {
userID, ok := UserIDFromContext(ctx)
if !ok {