diff --git a/gen/engpad/v1/sync.pb.go b/gen/engpad/v1/sync.pb.go new file mode 100644 index 0000000..455e183 --- /dev/null +++ b/gen/engpad/v1/sync.pb.go @@ -0,0 +1,1031 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.20.3 +// source: engpad/v1/sync.proto + +package engpadv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SyncNotebookRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NotebookId int64 `protobuf:"varint,1,opt,name=notebook_id,json=notebookId,proto3" json:"notebook_id,omitempty"` + Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` + PageSize string `protobuf:"bytes,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + Pages []*PageData `protobuf:"bytes,4,rep,name=pages,proto3" json:"pages,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncNotebookRequest) Reset() { + *x = SyncNotebookRequest{} + mi := &file_engpad_v1_sync_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncNotebookRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncNotebookRequest) ProtoMessage() {} + +func (x *SyncNotebookRequest) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncNotebookRequest.ProtoReflect.Descriptor instead. +func (*SyncNotebookRequest) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{0} +} + +func (x *SyncNotebookRequest) GetNotebookId() int64 { + if x != nil { + return x.NotebookId + } + return 0 +} + +func (x *SyncNotebookRequest) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *SyncNotebookRequest) GetPageSize() string { + if x != nil { + return x.PageSize + } + return "" +} + +func (x *SyncNotebookRequest) GetPages() []*PageData { + if x != nil { + return x.Pages + } + return nil +} + +type PageData struct { + state protoimpl.MessageState `protogen:"open.v1"` + PageId int64 `protobuf:"varint,1,opt,name=page_id,json=pageId,proto3" json:"page_id,omitempty"` + PageNumber int32 `protobuf:"varint,2,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` + Strokes []*StrokeData `protobuf:"bytes,3,rep,name=strokes,proto3" json:"strokes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PageData) Reset() { + *x = PageData{} + mi := &file_engpad_v1_sync_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PageData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PageData) ProtoMessage() {} + +func (x *PageData) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PageData.ProtoReflect.Descriptor instead. +func (*PageData) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{1} +} + +func (x *PageData) GetPageId() int64 { + if x != nil { + return x.PageId + } + return 0 +} + +func (x *PageData) GetPageNumber() int32 { + if x != nil { + return x.PageNumber + } + return 0 +} + +func (x *PageData) GetStrokes() []*StrokeData { + if x != nil { + return x.Strokes + } + return nil +} + +type StrokeData struct { + state protoimpl.MessageState `protogen:"open.v1"` + PenSize float32 `protobuf:"fixed32,1,opt,name=pen_size,json=penSize,proto3" json:"pen_size,omitempty"` + Color int32 `protobuf:"varint,2,opt,name=color,proto3" json:"color,omitempty"` + Style string `protobuf:"bytes,3,opt,name=style,proto3" json:"style,omitempty"` + PointData []byte `protobuf:"bytes,4,opt,name=point_data,json=pointData,proto3" json:"point_data,omitempty"` + StrokeOrder int32 `protobuf:"varint,5,opt,name=stroke_order,json=strokeOrder,proto3" json:"stroke_order,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StrokeData) Reset() { + *x = StrokeData{} + mi := &file_engpad_v1_sync_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StrokeData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StrokeData) ProtoMessage() {} + +func (x *StrokeData) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StrokeData.ProtoReflect.Descriptor instead. +func (*StrokeData) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{2} +} + +func (x *StrokeData) GetPenSize() float32 { + if x != nil { + return x.PenSize + } + return 0 +} + +func (x *StrokeData) GetColor() int32 { + if x != nil { + return x.Color + } + return 0 +} + +func (x *StrokeData) GetStyle() string { + if x != nil { + return x.Style + } + return "" +} + +func (x *StrokeData) GetPointData() []byte { + if x != nil { + return x.PointData + } + return nil +} + +func (x *StrokeData) GetStrokeOrder() int32 { + if x != nil { + return x.StrokeOrder + } + return 0 +} + +type SyncNotebookResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ServerNotebookId int64 `protobuf:"varint,1,opt,name=server_notebook_id,json=serverNotebookId,proto3" json:"server_notebook_id,omitempty"` + SyncedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=synced_at,json=syncedAt,proto3" json:"synced_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncNotebookResponse) Reset() { + *x = SyncNotebookResponse{} + mi := &file_engpad_v1_sync_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncNotebookResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncNotebookResponse) ProtoMessage() {} + +func (x *SyncNotebookResponse) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncNotebookResponse.ProtoReflect.Descriptor instead. +func (*SyncNotebookResponse) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{3} +} + +func (x *SyncNotebookResponse) GetServerNotebookId() int64 { + if x != nil { + return x.ServerNotebookId + } + return 0 +} + +func (x *SyncNotebookResponse) GetSyncedAt() *timestamppb.Timestamp { + if x != nil { + return x.SyncedAt + } + return nil +} + +type DeleteNotebookRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NotebookId int64 `protobuf:"varint,1,opt,name=notebook_id,json=notebookId,proto3" json:"notebook_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteNotebookRequest) Reset() { + *x = DeleteNotebookRequest{} + mi := &file_engpad_v1_sync_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteNotebookRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteNotebookRequest) ProtoMessage() {} + +func (x *DeleteNotebookRequest) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteNotebookRequest.ProtoReflect.Descriptor instead. +func (*DeleteNotebookRequest) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{4} +} + +func (x *DeleteNotebookRequest) GetNotebookId() int64 { + if x != nil { + return x.NotebookId + } + return 0 +} + +type DeleteNotebookResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteNotebookResponse) Reset() { + *x = DeleteNotebookResponse{} + mi := &file_engpad_v1_sync_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteNotebookResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteNotebookResponse) ProtoMessage() {} + +func (x *DeleteNotebookResponse) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteNotebookResponse.ProtoReflect.Descriptor instead. +func (*DeleteNotebookResponse) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{5} +} + +type ListNotebooksRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListNotebooksRequest) Reset() { + *x = ListNotebooksRequest{} + mi := &file_engpad_v1_sync_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListNotebooksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListNotebooksRequest) ProtoMessage() {} + +func (x *ListNotebooksRequest) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListNotebooksRequest.ProtoReflect.Descriptor instead. +func (*ListNotebooksRequest) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{6} +} + +type ListNotebooksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Notebooks []*NotebookSummary `protobuf:"bytes,1,rep,name=notebooks,proto3" json:"notebooks,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListNotebooksResponse) Reset() { + *x = ListNotebooksResponse{} + mi := &file_engpad_v1_sync_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListNotebooksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListNotebooksResponse) ProtoMessage() {} + +func (x *ListNotebooksResponse) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListNotebooksResponse.ProtoReflect.Descriptor instead. +func (*ListNotebooksResponse) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{7} +} + +func (x *ListNotebooksResponse) GetNotebooks() []*NotebookSummary { + if x != nil { + return x.Notebooks + } + return nil +} + +type NotebookSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + ServerId int64 `protobuf:"varint,1,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty"` + RemoteId int64 `protobuf:"varint,2,opt,name=remote_id,json=remoteId,proto3" json:"remote_id,omitempty"` + Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` + PageSize string `protobuf:"bytes,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + PageCount int32 `protobuf:"varint,5,opt,name=page_count,json=pageCount,proto3" json:"page_count,omitempty"` + SyncedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=synced_at,json=syncedAt,proto3" json:"synced_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NotebookSummary) Reset() { + *x = NotebookSummary{} + mi := &file_engpad_v1_sync_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NotebookSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotebookSummary) ProtoMessage() {} + +func (x *NotebookSummary) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NotebookSummary.ProtoReflect.Descriptor instead. +func (*NotebookSummary) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{8} +} + +func (x *NotebookSummary) GetServerId() int64 { + if x != nil { + return x.ServerId + } + return 0 +} + +func (x *NotebookSummary) GetRemoteId() int64 { + if x != nil { + return x.RemoteId + } + return 0 +} + +func (x *NotebookSummary) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *NotebookSummary) GetPageSize() string { + if x != nil { + return x.PageSize + } + return "" +} + +func (x *NotebookSummary) GetPageCount() int32 { + if x != nil { + return x.PageCount + } + return 0 +} + +func (x *NotebookSummary) GetSyncedAt() *timestamppb.Timestamp { + if x != nil { + return x.SyncedAt + } + return nil +} + +type CreateShareLinkRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NotebookId int64 `protobuf:"varint,1,opt,name=notebook_id,json=notebookId,proto3" json:"notebook_id,omitempty"` + ExpiresInSeconds int64 `protobuf:"varint,2,opt,name=expires_in_seconds,json=expiresInSeconds,proto3" json:"expires_in_seconds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateShareLinkRequest) Reset() { + *x = CreateShareLinkRequest{} + mi := &file_engpad_v1_sync_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateShareLinkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateShareLinkRequest) ProtoMessage() {} + +func (x *CreateShareLinkRequest) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateShareLinkRequest.ProtoReflect.Descriptor instead. +func (*CreateShareLinkRequest) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{9} +} + +func (x *CreateShareLinkRequest) GetNotebookId() int64 { + if x != nil { + return x.NotebookId + } + return 0 +} + +func (x *CreateShareLinkRequest) GetExpiresInSeconds() int64 { + if x != nil { + return x.ExpiresInSeconds + } + return 0 +} + +type CreateShareLinkResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateShareLinkResponse) Reset() { + *x = CreateShareLinkResponse{} + mi := &file_engpad_v1_sync_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateShareLinkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateShareLinkResponse) ProtoMessage() {} + +func (x *CreateShareLinkResponse) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateShareLinkResponse.ProtoReflect.Descriptor instead. +func (*CreateShareLinkResponse) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{10} +} + +func (x *CreateShareLinkResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *CreateShareLinkResponse) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *CreateShareLinkResponse) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +type RevokeShareLinkRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeShareLinkRequest) Reset() { + *x = RevokeShareLinkRequest{} + mi := &file_engpad_v1_sync_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeShareLinkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeShareLinkRequest) ProtoMessage() {} + +func (x *RevokeShareLinkRequest) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeShareLinkRequest.ProtoReflect.Descriptor instead. +func (*RevokeShareLinkRequest) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{11} +} + +func (x *RevokeShareLinkRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +type RevokeShareLinkResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeShareLinkResponse) Reset() { + *x = RevokeShareLinkResponse{} + mi := &file_engpad_v1_sync_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeShareLinkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeShareLinkResponse) ProtoMessage() {} + +func (x *RevokeShareLinkResponse) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeShareLinkResponse.ProtoReflect.Descriptor instead. +func (*RevokeShareLinkResponse) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{12} +} + +type ListShareLinksRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NotebookId int64 `protobuf:"varint,1,opt,name=notebook_id,json=notebookId,proto3" json:"notebook_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListShareLinksRequest) Reset() { + *x = ListShareLinksRequest{} + mi := &file_engpad_v1_sync_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListShareLinksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListShareLinksRequest) ProtoMessage() {} + +func (x *ListShareLinksRequest) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListShareLinksRequest.ProtoReflect.Descriptor instead. +func (*ListShareLinksRequest) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{13} +} + +func (x *ListShareLinksRequest) GetNotebookId() int64 { + if x != nil { + return x.NotebookId + } + return 0 +} + +type ListShareLinksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Links []*ShareLinkInfo `protobuf:"bytes,1,rep,name=links,proto3" json:"links,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListShareLinksResponse) Reset() { + *x = ListShareLinksResponse{} + mi := &file_engpad_v1_sync_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListShareLinksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListShareLinksResponse) ProtoMessage() {} + +func (x *ListShareLinksResponse) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListShareLinksResponse.ProtoReflect.Descriptor instead. +func (*ListShareLinksResponse) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{14} +} + +func (x *ListShareLinksResponse) GetLinks() []*ShareLinkInfo { + if x != nil { + return x.Links + } + return nil +} + +type ShareLinkInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ShareLinkInfo) Reset() { + *x = ShareLinkInfo{} + mi := &file_engpad_v1_sync_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ShareLinkInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShareLinkInfo) ProtoMessage() {} + +func (x *ShareLinkInfo) ProtoReflect() protoreflect.Message { + mi := &file_engpad_v1_sync_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShareLinkInfo.ProtoReflect.Descriptor instead. +func (*ShareLinkInfo) Descriptor() ([]byte, []int) { + return file_engpad_v1_sync_proto_rawDescGZIP(), []int{15} +} + +func (x *ShareLinkInfo) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *ShareLinkInfo) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *ShareLinkInfo) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *ShareLinkInfo) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +var File_engpad_v1_sync_proto protoreflect.FileDescriptor + +const file_engpad_v1_sync_proto_rawDesc = "" + + "\n" + + "\x14engpad/v1/sync.proto\x12\tengpad.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x94\x01\n" + + "\x13SyncNotebookRequest\x12\x1f\n" + + "\vnotebook_id\x18\x01 \x01(\x03R\n" + + "notebookId\x12\x14\n" + + "\x05title\x18\x02 \x01(\tR\x05title\x12\x1b\n" + + "\tpage_size\x18\x03 \x01(\tR\bpageSize\x12)\n" + + "\x05pages\x18\x04 \x03(\v2\x13.engpad.v1.PageDataR\x05pages\"u\n" + + "\bPageData\x12\x17\n" + + "\apage_id\x18\x01 \x01(\x03R\x06pageId\x12\x1f\n" + + "\vpage_number\x18\x02 \x01(\x05R\n" + + "pageNumber\x12/\n" + + "\astrokes\x18\x03 \x03(\v2\x15.engpad.v1.StrokeDataR\astrokes\"\x95\x01\n" + + "\n" + + "StrokeData\x12\x19\n" + + "\bpen_size\x18\x01 \x01(\x02R\apenSize\x12\x14\n" + + "\x05color\x18\x02 \x01(\x05R\x05color\x12\x14\n" + + "\x05style\x18\x03 \x01(\tR\x05style\x12\x1d\n" + + "\n" + + "point_data\x18\x04 \x01(\fR\tpointData\x12!\n" + + "\fstroke_order\x18\x05 \x01(\x05R\vstrokeOrder\"}\n" + + "\x14SyncNotebookResponse\x12,\n" + + "\x12server_notebook_id\x18\x01 \x01(\x03R\x10serverNotebookId\x127\n" + + "\tsynced_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncedAt\"8\n" + + "\x15DeleteNotebookRequest\x12\x1f\n" + + "\vnotebook_id\x18\x01 \x01(\x03R\n" + + "notebookId\"\x18\n" + + "\x16DeleteNotebookResponse\"\x16\n" + + "\x14ListNotebooksRequest\"Q\n" + + "\x15ListNotebooksResponse\x128\n" + + "\tnotebooks\x18\x01 \x03(\v2\x1a.engpad.v1.NotebookSummaryR\tnotebooks\"\xd6\x01\n" + + "\x0fNotebookSummary\x12\x1b\n" + + "\tserver_id\x18\x01 \x01(\x03R\bserverId\x12\x1b\n" + + "\tremote_id\x18\x02 \x01(\x03R\bremoteId\x12\x14\n" + + "\x05title\x18\x03 \x01(\tR\x05title\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\tR\bpageSize\x12\x1d\n" + + "\n" + + "page_count\x18\x05 \x01(\x05R\tpageCount\x127\n" + + "\tsynced_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncedAt\"g\n" + + "\x16CreateShareLinkRequest\x12\x1f\n" + + "\vnotebook_id\x18\x01 \x01(\x03R\n" + + "notebookId\x12,\n" + + "\x12expires_in_seconds\x18\x02 \x01(\x03R\x10expiresInSeconds\"|\n" + + "\x17CreateShareLinkResponse\x12\x14\n" + + "\x05token\x18\x01 \x01(\tR\x05token\x12\x10\n" + + "\x03url\x18\x02 \x01(\tR\x03url\x129\n" + + "\n" + + "expires_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\".\n" + + "\x16RevokeShareLinkRequest\x12\x14\n" + + "\x05token\x18\x01 \x01(\tR\x05token\"\x19\n" + + "\x17RevokeShareLinkResponse\"8\n" + + "\x15ListShareLinksRequest\x12\x1f\n" + + "\vnotebook_id\x18\x01 \x01(\x03R\n" + + "notebookId\"H\n" + + "\x16ListShareLinksResponse\x12.\n" + + "\x05links\x18\x01 \x03(\v2\x18.engpad.v1.ShareLinkInfoR\x05links\"\xad\x01\n" + + "\rShareLinkInfo\x12\x14\n" + + "\x05token\x18\x01 \x01(\tR\x05token\x12\x10\n" + + "\x03url\x18\x02 \x01(\tR\x03url\x129\n" + + "\n" + + "created_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt2\x93\x04\n" + + "\n" + + "EngPadSync\x12O\n" + + "\fSyncNotebook\x12\x1e.engpad.v1.SyncNotebookRequest\x1a\x1f.engpad.v1.SyncNotebookResponse\x12U\n" + + "\x0eDeleteNotebook\x12 .engpad.v1.DeleteNotebookRequest\x1a!.engpad.v1.DeleteNotebookResponse\x12R\n" + + "\rListNotebooks\x12\x1f.engpad.v1.ListNotebooksRequest\x1a .engpad.v1.ListNotebooksResponse\x12X\n" + + "\x0fCreateShareLink\x12!.engpad.v1.CreateShareLinkRequest\x1a\".engpad.v1.CreateShareLinkResponse\x12X\n" + + "\x0fRevokeShareLink\x12!.engpad.v1.RevokeShareLinkRequest\x1a\".engpad.v1.RevokeShareLinkResponse\x12U\n" + + "\x0eListShareLinks\x12 .engpad.v1.ListShareLinksRequest\x1a!.engpad.v1.ListShareLinksResponseB=Z;git.wntrmute.dev/kyle/eng-pad-server/gen/engpad/v1;engpadv1b\x06proto3" + +var ( + file_engpad_v1_sync_proto_rawDescOnce sync.Once + file_engpad_v1_sync_proto_rawDescData []byte +) + +func file_engpad_v1_sync_proto_rawDescGZIP() []byte { + file_engpad_v1_sync_proto_rawDescOnce.Do(func() { + file_engpad_v1_sync_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_engpad_v1_sync_proto_rawDesc), len(file_engpad_v1_sync_proto_rawDesc))) + }) + return file_engpad_v1_sync_proto_rawDescData +} + +var file_engpad_v1_sync_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_engpad_v1_sync_proto_goTypes = []any{ + (*SyncNotebookRequest)(nil), // 0: engpad.v1.SyncNotebookRequest + (*PageData)(nil), // 1: engpad.v1.PageData + (*StrokeData)(nil), // 2: engpad.v1.StrokeData + (*SyncNotebookResponse)(nil), // 3: engpad.v1.SyncNotebookResponse + (*DeleteNotebookRequest)(nil), // 4: engpad.v1.DeleteNotebookRequest + (*DeleteNotebookResponse)(nil), // 5: engpad.v1.DeleteNotebookResponse + (*ListNotebooksRequest)(nil), // 6: engpad.v1.ListNotebooksRequest + (*ListNotebooksResponse)(nil), // 7: engpad.v1.ListNotebooksResponse + (*NotebookSummary)(nil), // 8: engpad.v1.NotebookSummary + (*CreateShareLinkRequest)(nil), // 9: engpad.v1.CreateShareLinkRequest + (*CreateShareLinkResponse)(nil), // 10: engpad.v1.CreateShareLinkResponse + (*RevokeShareLinkRequest)(nil), // 11: engpad.v1.RevokeShareLinkRequest + (*RevokeShareLinkResponse)(nil), // 12: engpad.v1.RevokeShareLinkResponse + (*ListShareLinksRequest)(nil), // 13: engpad.v1.ListShareLinksRequest + (*ListShareLinksResponse)(nil), // 14: engpad.v1.ListShareLinksResponse + (*ShareLinkInfo)(nil), // 15: engpad.v1.ShareLinkInfo + (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp +} +var file_engpad_v1_sync_proto_depIdxs = []int32{ + 1, // 0: engpad.v1.SyncNotebookRequest.pages:type_name -> engpad.v1.PageData + 2, // 1: engpad.v1.PageData.strokes:type_name -> engpad.v1.StrokeData + 16, // 2: engpad.v1.SyncNotebookResponse.synced_at:type_name -> google.protobuf.Timestamp + 8, // 3: engpad.v1.ListNotebooksResponse.notebooks:type_name -> engpad.v1.NotebookSummary + 16, // 4: engpad.v1.NotebookSummary.synced_at:type_name -> google.protobuf.Timestamp + 16, // 5: engpad.v1.CreateShareLinkResponse.expires_at:type_name -> google.protobuf.Timestamp + 15, // 6: engpad.v1.ListShareLinksResponse.links:type_name -> engpad.v1.ShareLinkInfo + 16, // 7: engpad.v1.ShareLinkInfo.created_at:type_name -> google.protobuf.Timestamp + 16, // 8: engpad.v1.ShareLinkInfo.expires_at:type_name -> google.protobuf.Timestamp + 0, // 9: engpad.v1.EngPadSync.SyncNotebook:input_type -> engpad.v1.SyncNotebookRequest + 4, // 10: engpad.v1.EngPadSync.DeleteNotebook:input_type -> engpad.v1.DeleteNotebookRequest + 6, // 11: engpad.v1.EngPadSync.ListNotebooks:input_type -> engpad.v1.ListNotebooksRequest + 9, // 12: engpad.v1.EngPadSync.CreateShareLink:input_type -> engpad.v1.CreateShareLinkRequest + 11, // 13: engpad.v1.EngPadSync.RevokeShareLink:input_type -> engpad.v1.RevokeShareLinkRequest + 13, // 14: engpad.v1.EngPadSync.ListShareLinks:input_type -> engpad.v1.ListShareLinksRequest + 3, // 15: engpad.v1.EngPadSync.SyncNotebook:output_type -> engpad.v1.SyncNotebookResponse + 5, // 16: engpad.v1.EngPadSync.DeleteNotebook:output_type -> engpad.v1.DeleteNotebookResponse + 7, // 17: engpad.v1.EngPadSync.ListNotebooks:output_type -> engpad.v1.ListNotebooksResponse + 10, // 18: engpad.v1.EngPadSync.CreateShareLink:output_type -> engpad.v1.CreateShareLinkResponse + 12, // 19: engpad.v1.EngPadSync.RevokeShareLink:output_type -> engpad.v1.RevokeShareLinkResponse + 14, // 20: engpad.v1.EngPadSync.ListShareLinks:output_type -> engpad.v1.ListShareLinksResponse + 15, // [15:21] is the sub-list for method output_type + 9, // [9:15] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_engpad_v1_sync_proto_init() } +func file_engpad_v1_sync_proto_init() { + if File_engpad_v1_sync_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_engpad_v1_sync_proto_rawDesc), len(file_engpad_v1_sync_proto_rawDesc)), + NumEnums: 0, + NumMessages: 16, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_engpad_v1_sync_proto_goTypes, + DependencyIndexes: file_engpad_v1_sync_proto_depIdxs, + MessageInfos: file_engpad_v1_sync_proto_msgTypes, + }.Build() + File_engpad_v1_sync_proto = out.File + file_engpad_v1_sync_proto_goTypes = nil + file_engpad_v1_sync_proto_depIdxs = nil +} diff --git a/gen/engpad/v1/sync_grpc.pb.go b/gen/engpad/v1/sync_grpc.pb.go new file mode 100644 index 0000000..0c42dbc --- /dev/null +++ b/gen/engpad/v1/sync_grpc.pb.go @@ -0,0 +1,311 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.20.3 +// source: engpad/v1/sync.proto + +package engpadv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + EngPadSync_SyncNotebook_FullMethodName = "/engpad.v1.EngPadSync/SyncNotebook" + EngPadSync_DeleteNotebook_FullMethodName = "/engpad.v1.EngPadSync/DeleteNotebook" + EngPadSync_ListNotebooks_FullMethodName = "/engpad.v1.EngPadSync/ListNotebooks" + EngPadSync_CreateShareLink_FullMethodName = "/engpad.v1.EngPadSync/CreateShareLink" + EngPadSync_RevokeShareLink_FullMethodName = "/engpad.v1.EngPadSync/RevokeShareLink" + EngPadSync_ListShareLinks_FullMethodName = "/engpad.v1.EngPadSync/ListShareLinks" +) + +// EngPadSyncClient is the client API for EngPadSync service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type EngPadSyncClient interface { + SyncNotebook(ctx context.Context, in *SyncNotebookRequest, opts ...grpc.CallOption) (*SyncNotebookResponse, error) + DeleteNotebook(ctx context.Context, in *DeleteNotebookRequest, opts ...grpc.CallOption) (*DeleteNotebookResponse, error) + ListNotebooks(ctx context.Context, in *ListNotebooksRequest, opts ...grpc.CallOption) (*ListNotebooksResponse, error) + CreateShareLink(ctx context.Context, in *CreateShareLinkRequest, opts ...grpc.CallOption) (*CreateShareLinkResponse, error) + RevokeShareLink(ctx context.Context, in *RevokeShareLinkRequest, opts ...grpc.CallOption) (*RevokeShareLinkResponse, error) + ListShareLinks(ctx context.Context, in *ListShareLinksRequest, opts ...grpc.CallOption) (*ListShareLinksResponse, error) +} + +type engPadSyncClient struct { + cc grpc.ClientConnInterface +} + +func NewEngPadSyncClient(cc grpc.ClientConnInterface) EngPadSyncClient { + return &engPadSyncClient{cc} +} + +func (c *engPadSyncClient) SyncNotebook(ctx context.Context, in *SyncNotebookRequest, opts ...grpc.CallOption) (*SyncNotebookResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SyncNotebookResponse) + err := c.cc.Invoke(ctx, EngPadSync_SyncNotebook_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engPadSyncClient) DeleteNotebook(ctx context.Context, in *DeleteNotebookRequest, opts ...grpc.CallOption) (*DeleteNotebookResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteNotebookResponse) + err := c.cc.Invoke(ctx, EngPadSync_DeleteNotebook_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engPadSyncClient) ListNotebooks(ctx context.Context, in *ListNotebooksRequest, opts ...grpc.CallOption) (*ListNotebooksResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListNotebooksResponse) + err := c.cc.Invoke(ctx, EngPadSync_ListNotebooks_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engPadSyncClient) CreateShareLink(ctx context.Context, in *CreateShareLinkRequest, opts ...grpc.CallOption) (*CreateShareLinkResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateShareLinkResponse) + err := c.cc.Invoke(ctx, EngPadSync_CreateShareLink_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engPadSyncClient) RevokeShareLink(ctx context.Context, in *RevokeShareLinkRequest, opts ...grpc.CallOption) (*RevokeShareLinkResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RevokeShareLinkResponse) + err := c.cc.Invoke(ctx, EngPadSync_RevokeShareLink_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engPadSyncClient) ListShareLinks(ctx context.Context, in *ListShareLinksRequest, opts ...grpc.CallOption) (*ListShareLinksResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListShareLinksResponse) + err := c.cc.Invoke(ctx, EngPadSync_ListShareLinks_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EngPadSyncServer is the server API for EngPadSync service. +// All implementations must embed UnimplementedEngPadSyncServer +// for forward compatibility. +type EngPadSyncServer interface { + SyncNotebook(context.Context, *SyncNotebookRequest) (*SyncNotebookResponse, error) + DeleteNotebook(context.Context, *DeleteNotebookRequest) (*DeleteNotebookResponse, error) + ListNotebooks(context.Context, *ListNotebooksRequest) (*ListNotebooksResponse, error) + CreateShareLink(context.Context, *CreateShareLinkRequest) (*CreateShareLinkResponse, error) + RevokeShareLink(context.Context, *RevokeShareLinkRequest) (*RevokeShareLinkResponse, error) + ListShareLinks(context.Context, *ListShareLinksRequest) (*ListShareLinksResponse, error) + mustEmbedUnimplementedEngPadSyncServer() +} + +// UnimplementedEngPadSyncServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedEngPadSyncServer struct{} + +func (UnimplementedEngPadSyncServer) SyncNotebook(context.Context, *SyncNotebookRequest) (*SyncNotebookResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SyncNotebook not implemented") +} +func (UnimplementedEngPadSyncServer) DeleteNotebook(context.Context, *DeleteNotebookRequest) (*DeleteNotebookResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteNotebook not implemented") +} +func (UnimplementedEngPadSyncServer) ListNotebooks(context.Context, *ListNotebooksRequest) (*ListNotebooksResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListNotebooks not implemented") +} +func (UnimplementedEngPadSyncServer) CreateShareLink(context.Context, *CreateShareLinkRequest) (*CreateShareLinkResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateShareLink not implemented") +} +func (UnimplementedEngPadSyncServer) RevokeShareLink(context.Context, *RevokeShareLinkRequest) (*RevokeShareLinkResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RevokeShareLink not implemented") +} +func (UnimplementedEngPadSyncServer) ListShareLinks(context.Context, *ListShareLinksRequest) (*ListShareLinksResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListShareLinks not implemented") +} +func (UnimplementedEngPadSyncServer) mustEmbedUnimplementedEngPadSyncServer() {} +func (UnimplementedEngPadSyncServer) testEmbeddedByValue() {} + +// UnsafeEngPadSyncServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EngPadSyncServer will +// result in compilation errors. +type UnsafeEngPadSyncServer interface { + mustEmbedUnimplementedEngPadSyncServer() +} + +func RegisterEngPadSyncServer(s grpc.ServiceRegistrar, srv EngPadSyncServer) { + // If the following call panics, it indicates UnimplementedEngPadSyncServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&EngPadSync_ServiceDesc, srv) +} + +func _EngPadSync_SyncNotebook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SyncNotebookRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngPadSyncServer).SyncNotebook(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngPadSync_SyncNotebook_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngPadSyncServer).SyncNotebook(ctx, req.(*SyncNotebookRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngPadSync_DeleteNotebook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteNotebookRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngPadSyncServer).DeleteNotebook(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngPadSync_DeleteNotebook_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngPadSyncServer).DeleteNotebook(ctx, req.(*DeleteNotebookRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngPadSync_ListNotebooks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListNotebooksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngPadSyncServer).ListNotebooks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngPadSync_ListNotebooks_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngPadSyncServer).ListNotebooks(ctx, req.(*ListNotebooksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngPadSync_CreateShareLink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateShareLinkRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngPadSyncServer).CreateShareLink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngPadSync_CreateShareLink_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngPadSyncServer).CreateShareLink(ctx, req.(*CreateShareLinkRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngPadSync_RevokeShareLink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeShareLinkRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngPadSyncServer).RevokeShareLink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngPadSync_RevokeShareLink_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngPadSyncServer).RevokeShareLink(ctx, req.(*RevokeShareLinkRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngPadSync_ListShareLinks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListShareLinksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngPadSyncServer).ListShareLinks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngPadSync_ListShareLinks_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngPadSyncServer).ListShareLinks(ctx, req.(*ListShareLinksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// EngPadSync_ServiceDesc is the grpc.ServiceDesc for EngPadSync service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var EngPadSync_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "engpad.v1.EngPadSync", + HandlerType: (*EngPadSyncServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SyncNotebook", + Handler: _EngPadSync_SyncNotebook_Handler, + }, + { + MethodName: "DeleteNotebook", + Handler: _EngPadSync_DeleteNotebook_Handler, + }, + { + MethodName: "ListNotebooks", + Handler: _EngPadSync_ListNotebooks_Handler, + }, + { + MethodName: "CreateShareLink", + Handler: _EngPadSync_CreateShareLink_Handler, + }, + { + MethodName: "RevokeShareLink", + Handler: _EngPadSync_RevokeShareLink_Handler, + }, + { + MethodName: "ListShareLinks", + Handler: _EngPadSync_ListShareLinks_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "engpad/v1/sync.proto", +} diff --git a/go.mod b/go.mod index 75d7ab2..e04ce7c 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/spf13/cobra v1.10.2 golang.org/x/crypto v0.49.0 golang.org/x/term v0.41.0 + google.golang.org/grpc v1.79.3 + google.golang.org/protobuf v1.36.11 modernc.org/sqlite v1.47.0 ) @@ -18,7 +20,10 @@ require ( github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/pflag v1.0.9 // indirect + golang.org/x/net v0.51.0 // indirect golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 90994c5..d438ee9 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,16 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -22,20 +32,44 @@ github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= diff --git a/internal/grpcserver/interceptors.go b/internal/grpcserver/interceptors.go new file mode 100644 index 0000000..f076b2c --- /dev/null +++ b/internal/grpcserver/interceptors.go @@ -0,0 +1,46 @@ +package grpcserver + +import ( + "context" + "database/sql" + + "git.wntrmute.dev/kyle/eng-pad-server/internal/auth" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +type contextKey string + +const userIDKey contextKey = "user_id" + +// UserIDFromContext extracts the authenticated user ID from the context. +func UserIDFromContext(ctx context.Context) (int64, bool) { + id, ok := ctx.Value(userIDKey).(int64) + return id, ok +} + +// AuthInterceptor verifies username/password from gRPC metadata. +func AuthInterceptor(database *sql.DB) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Error(codes.Unauthenticated, "missing metadata") + } + + usernames := md.Get("username") + passwords := md.Get("password") + if len(usernames) == 0 || len(passwords) == 0 { + return nil, status.Error(codes.Unauthenticated, "missing credentials") + } + + userID, err := auth.AuthenticateUser(database, usernames[0], passwords[0]) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "invalid credentials") + } + + ctx = context.WithValue(ctx, userIDKey, userID) + return handler(ctx, req) + } +} diff --git a/internal/grpcserver/server.go b/internal/grpcserver/server.go new file mode 100644 index 0000000..2d9a7f5 --- /dev/null +++ b/internal/grpcserver/server.go @@ -0,0 +1,48 @@ +package grpcserver + +import ( + "crypto/tls" + "database/sql" + "fmt" + "net" + + pb "git.wntrmute.dev/kyle/eng-pad-server/gen/engpad/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +type Config struct { + Addr string + TLSCert string + TLSKey string + DB *sql.DB + BaseURL string +} + +func Start(cfg Config) error { + cert, err := tls.LoadX509KeyPair(cfg.TLSCert, cfg.TLSKey) + if err != nil { + return fmt.Errorf("load TLS cert: %w", err) + } + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS13, + } + + lis, err := net.Listen("tcp", cfg.Addr) + if err != nil { + return fmt.Errorf("listen %s: %w", cfg.Addr, err) + } + + srv := grpc.NewServer( + grpc.Creds(credentials.NewTLS(tlsConfig)), + grpc.UnaryInterceptor(AuthInterceptor(cfg.DB)), + ) + + syncSvc := &SyncService{DB: cfg.DB, BaseURL: cfg.BaseURL} + pb.RegisterEngPadSyncServer(srv, syncSvc) + + fmt.Printf("gRPC listening on %s\n", cfg.Addr) + return srv.Serve(lis) +} diff --git a/internal/grpcserver/share.go b/internal/grpcserver/share.go new file mode 100644 index 0000000..b7a7fee --- /dev/null +++ b/internal/grpcserver/share.go @@ -0,0 +1,94 @@ +package grpcserver + +import ( + "context" + "database/sql" + "time" + + pb "git.wntrmute.dev/kyle/eng-pad-server/gen/engpad/v1" + "git.wntrmute.dev/kyle/eng-pad-server/internal/share" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func (s *SyncService) CreateShareLink(ctx context.Context, req *pb.CreateShareLinkRequest) (*pb.CreateShareLinkResponse, error) { + userID, ok := UserIDFromContext(ctx) + if !ok { + return nil, status.Error(codes.Internal, "missing user context") + } + + // Verify notebook belongs to user + var notebookID int64 + err := s.DB.QueryRowContext(ctx, + "SELECT id FROM notebooks WHERE user_id = ? AND remote_id = ?", + userID, req.NotebookId, + ).Scan(¬ebookID) + if err == sql.ErrNoRows { + return nil, status.Error(codes.NotFound, "notebook not found") + } else if err != nil { + return nil, status.Errorf(codes.Internal, "query: %v", err) + } + + var expiry time.Duration + if req.ExpiresInSeconds > 0 { + expiry = time.Duration(req.ExpiresInSeconds) * time.Second + } + + token, expiresAt, err := share.CreateLink(s.DB, notebookID, expiry, s.BaseURL) + if err != nil { + return nil, status.Errorf(codes.Internal, "create link: %v", err) + } + + resp := &pb.CreateShareLinkResponse{ + Token: token, + Url: s.BaseURL + "/s/" + token, + } + if expiresAt != nil { + resp.ExpiresAt = timestamppb.New(*expiresAt) + } + return resp, nil +} + +func (s *SyncService) RevokeShareLink(ctx context.Context, req *pb.RevokeShareLinkRequest) (*pb.RevokeShareLinkResponse, error) { + if err := share.RevokeLink(s.DB, req.Token); err != nil { + return nil, status.Errorf(codes.Internal, "revoke: %v", err) + } + return &pb.RevokeShareLinkResponse{}, nil +} + +func (s *SyncService) ListShareLinks(ctx context.Context, req *pb.ListShareLinksRequest) (*pb.ListShareLinksResponse, error) { + userID, ok := UserIDFromContext(ctx) + if !ok { + return nil, status.Error(codes.Internal, "missing user context") + } + + var notebookID int64 + err := s.DB.QueryRowContext(ctx, + "SELECT id FROM notebooks WHERE user_id = ? AND remote_id = ?", + userID, req.NotebookId, + ).Scan(¬ebookID) + if err != nil { + return nil, status.Error(codes.NotFound, "notebook not found") + } + + links, err := share.ListLinks(s.DB, notebookID, s.BaseURL) + if err != nil { + return nil, status.Errorf(codes.Internal, "list: %v", err) + } + + var pbLinks []*pb.ShareLinkInfo + for _, l := range links { + pbl := &pb.ShareLinkInfo{ + Token: l.Token, + Url: l.URL, + CreatedAt: timestamppb.New(l.CreatedAt), + } + if l.ExpiresAt != nil { + pbl.ExpiresAt = timestamppb.New(*l.ExpiresAt) + } + pbLinks = append(pbLinks, pbl) + } + + return &pb.ListShareLinksResponse{Links: pbLinks}, nil +} diff --git a/internal/grpcserver/sync.go b/internal/grpcserver/sync.go new file mode 100644 index 0000000..5a8290f --- /dev/null +++ b/internal/grpcserver/sync.go @@ -0,0 +1,142 @@ +package grpcserver + +import ( + "context" + "database/sql" + "time" + + pb "git.wntrmute.dev/kyle/eng-pad-server/gen/engpad/v1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type SyncService struct { + pb.UnimplementedEngPadSyncServer + DB *sql.DB + BaseURL string +} + +func (s *SyncService) SyncNotebook(ctx context.Context, req *pb.SyncNotebookRequest) (*pb.SyncNotebookResponse, error) { + userID, ok := UserIDFromContext(ctx) + if !ok { + return nil, status.Error(codes.Internal, "missing user context") + } + + tx, err := s.DB.BeginTx(ctx, nil) + if err != nil { + return nil, status.Errorf(codes.Internal, "begin tx: %v", err) + } + defer func() { _ = tx.Rollback() }() + + now := time.Now().UnixMilli() + + // Upsert notebook + var notebookID int64 + err = tx.QueryRowContext(ctx, + "SELECT id FROM notebooks WHERE user_id = ? AND remote_id = ?", + userID, req.NotebookId, + ).Scan(¬ebookID) + + if err == sql.ErrNoRows { + res, err := tx.ExecContext(ctx, + "INSERT INTO notebooks (user_id, remote_id, title, page_size, synced_at) VALUES (?, ?, ?, ?, ?)", + userID, req.NotebookId, req.Title, req.PageSize, now, + ) + if err != nil { + return nil, status.Errorf(codes.Internal, "insert notebook: %v", err) + } + notebookID, _ = res.LastInsertId() + } else if err != nil { + return nil, status.Errorf(codes.Internal, "query notebook: %v", err) + } else { + // Update existing — delete all pages (cascade deletes strokes) + if _, err := tx.ExecContext(ctx, "DELETE FROM pages WHERE notebook_id = ?", notebookID); err != nil { + return nil, status.Errorf(codes.Internal, "delete pages: %v", err) + } + if _, err := tx.ExecContext(ctx, + "UPDATE notebooks SET title = ?, page_size = ?, synced_at = ? WHERE id = ?", + req.Title, req.PageSize, now, notebookID, + ); err != nil { + return nil, status.Errorf(codes.Internal, "update notebook: %v", err) + } + } + + // Insert pages and strokes + for _, page := range req.Pages { + res, err := tx.ExecContext(ctx, + "INSERT INTO pages (notebook_id, remote_id, page_number) VALUES (?, ?, ?)", + notebookID, page.PageId, page.PageNumber, + ) + if err != nil { + return nil, status.Errorf(codes.Internal, "insert page: %v", err) + } + pageID, _ := res.LastInsertId() + + for _, stroke := range page.Strokes { + if _, err := tx.ExecContext(ctx, + "INSERT INTO strokes (page_id, pen_size, color, style, point_data, stroke_order) VALUES (?, ?, ?, ?, ?, ?)", + pageID, stroke.PenSize, stroke.Color, stroke.Style, stroke.PointData, stroke.StrokeOrder, + ); err != nil { + return nil, status.Errorf(codes.Internal, "insert stroke: %v", err) + } + } + } + + if err := tx.Commit(); err != nil { + return nil, status.Errorf(codes.Internal, "commit: %v", err) + } + + return &pb.SyncNotebookResponse{ + ServerNotebookId: notebookID, + SyncedAt: timestamppb.Now(), + }, nil +} + +func (s *SyncService) DeleteNotebook(ctx context.Context, req *pb.DeleteNotebookRequest) (*pb.DeleteNotebookResponse, error) { + userID, ok := UserIDFromContext(ctx) + if !ok { + return nil, status.Error(codes.Internal, "missing user context") + } + + _, err := s.DB.ExecContext(ctx, + "DELETE FROM notebooks WHERE user_id = ? AND remote_id = ?", + userID, req.NotebookId, + ) + if err != nil { + return nil, status.Errorf(codes.Internal, "delete: %v", err) + } + + return &pb.DeleteNotebookResponse{}, nil +} + +func (s *SyncService) ListNotebooks(ctx context.Context, req *pb.ListNotebooksRequest) (*pb.ListNotebooksResponse, error) { + userID, ok := UserIDFromContext(ctx) + if !ok { + return nil, status.Error(codes.Internal, "missing user context") + } + + rows, err := s.DB.QueryContext(ctx, + `SELECT n.id, n.remote_id, n.title, n.page_size, n.synced_at, + (SELECT COUNT(*) FROM pages WHERE notebook_id = n.id) as page_count + FROM notebooks n WHERE n.user_id = ? ORDER BY n.synced_at DESC`, + userID, + ) + if err != nil { + return nil, status.Errorf(codes.Internal, "query: %v", err) + } + defer func() { _ = rows.Close() }() + + var notebooks []*pb.NotebookSummary + for rows.Next() { + var nb pb.NotebookSummary + var syncedAt int64 + if err := rows.Scan(&nb.ServerId, &nb.RemoteId, &nb.Title, &nb.PageSize, &syncedAt, &nb.PageCount); err != nil { + return nil, status.Errorf(codes.Internal, "scan: %v", err) + } + nb.SyncedAt = timestamppb.New(time.UnixMilli(syncedAt)) + notebooks = append(notebooks, &nb) + } + + return &pb.ListNotebooksResponse{Notebooks: notebooks}, nil +} diff --git a/internal/share/share.go b/internal/share/share.go new file mode 100644 index 0000000..7cd3e7e --- /dev/null +++ b/internal/share/share.go @@ -0,0 +1,105 @@ +package share + +import ( + "crypto/rand" + "database/sql" + "encoding/base64" + "fmt" + "time" +) + +const tokenBytes = 32 + +type LinkInfo struct { + Token string + URL string + CreatedAt time.Time + ExpiresAt *time.Time +} + +// CreateLink generates a shareable link for a notebook. +func CreateLink(database *sql.DB, notebookID int64, expiry time.Duration, baseURL string) (string, *time.Time, error) { + raw := make([]byte, tokenBytes) + if _, err := rand.Read(raw); err != nil { + return "", nil, fmt.Errorf("generate token: %w", err) + } + + token := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(raw) + now := time.Now().UnixMilli() + + var expiresAt *int64 + var expiresTime *time.Time + if expiry > 0 { + ea := time.Now().Add(expiry).UnixMilli() + expiresAt = &ea + t := time.UnixMilli(ea) + expiresTime = &t + } + + _, err := database.Exec( + "INSERT INTO share_links (notebook_id, token, expires_at, created_at) VALUES (?, ?, ?, ?)", + notebookID, token, expiresAt, now, + ) + if err != nil { + return "", nil, fmt.Errorf("insert share link: %w", err) + } + + return token, expiresTime, nil +} + +// ValidateLink checks if a token is valid and returns the notebook ID. +func ValidateLink(database *sql.DB, token string) (int64, error) { + var notebookID int64 + var expiresAt *int64 + err := database.QueryRow( + "SELECT notebook_id, expires_at FROM share_links WHERE token = ?", token, + ).Scan(¬ebookID, &expiresAt) + if err != nil { + return 0, fmt.Errorf("link not found") + } + + if expiresAt != nil && time.Now().UnixMilli() > *expiresAt { + return 0, fmt.Errorf("link expired") + } + + return notebookID, nil +} + +// RevokeLink deletes a share link. +func RevokeLink(database *sql.DB, token string) error { + _, err := database.Exec("DELETE FROM share_links WHERE token = ?", token) + return err +} + +// ListLinks returns all active share links for a notebook. +func ListLinks(database *sql.DB, notebookID int64, baseURL string) ([]LinkInfo, error) { + rows, err := database.Query( + "SELECT token, created_at, expires_at FROM share_links WHERE notebook_id = ? ORDER BY created_at DESC", + notebookID, + ) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var links []LinkInfo + for rows.Next() { + var token string + var createdAt int64 + var expiresAt *int64 + if err := rows.Scan(&token, &createdAt, &expiresAt); err != nil { + return nil, err + } + li := LinkInfo{ + Token: token, + URL: baseURL + "/s/" + token, + CreatedAt: time.UnixMilli(createdAt), + } + if expiresAt != nil { + t := time.UnixMilli(*expiresAt) + li.ExpiresAt = &t + } + links = append(links, li) + } + return links, nil +} diff --git a/proto/engpad/v1/sync.proto b/proto/engpad/v1/sync.proto new file mode 100644 index 0000000..1cbe28d --- /dev/null +++ b/proto/engpad/v1/sync.proto @@ -0,0 +1,94 @@ +syntax = "proto3"; +package engpad.v1; + +option go_package = "git.wntrmute.dev/kyle/eng-pad-server/gen/engpad/v1;engpadv1"; + +import "google/protobuf/timestamp.proto"; + +service EngPadSync { + rpc SyncNotebook(SyncNotebookRequest) returns (SyncNotebookResponse); + rpc DeleteNotebook(DeleteNotebookRequest) returns (DeleteNotebookResponse); + rpc ListNotebooks(ListNotebooksRequest) returns (ListNotebooksResponse); + rpc CreateShareLink(CreateShareLinkRequest) returns (CreateShareLinkResponse); + rpc RevokeShareLink(RevokeShareLinkRequest) returns (RevokeShareLinkResponse); + rpc ListShareLinks(ListShareLinksRequest) returns (ListShareLinksResponse); +} + +message SyncNotebookRequest { + int64 notebook_id = 1; + string title = 2; + string page_size = 3; + repeated PageData pages = 4; +} + +message PageData { + int64 page_id = 1; + int32 page_number = 2; + repeated StrokeData strokes = 3; +} + +message StrokeData { + float pen_size = 1; + int32 color = 2; + string style = 3; + bytes point_data = 4; + int32 stroke_order = 5; +} + +message SyncNotebookResponse { + int64 server_notebook_id = 1; + google.protobuf.Timestamp synced_at = 2; +} + +message DeleteNotebookRequest { + int64 notebook_id = 1; +} + +message DeleteNotebookResponse {} + +message ListNotebooksRequest {} + +message ListNotebooksResponse { + repeated NotebookSummary notebooks = 1; +} + +message NotebookSummary { + int64 server_id = 1; + int64 remote_id = 2; + string title = 3; + string page_size = 4; + int32 page_count = 5; + google.protobuf.Timestamp synced_at = 6; +} + +message CreateShareLinkRequest { + int64 notebook_id = 1; + int64 expires_in_seconds = 2; +} + +message CreateShareLinkResponse { + string token = 1; + string url = 2; + google.protobuf.Timestamp expires_at = 3; +} + +message RevokeShareLinkRequest { + string token = 1; +} + +message RevokeShareLinkResponse {} + +message ListShareLinksRequest { + int64 notebook_id = 1; +} + +message ListShareLinksResponse { + repeated ShareLinkInfo links = 1; +} + +message ShareLinkInfo { + string token = 1; + string url = 2; + google.protobuf.Timestamp created_at = 3; + google.protobuf.Timestamp expires_at = 4; +}