mirror of https://github.com/usememos/memos.git
refactor(inbox): store memo comment payloads without activity records (#5741)
Co-authored-by: memoclaw <265580040+memoclaw@users.noreply.github.com>
This commit is contained in:
parent
a249d06e2e
commit
f759b416af
|
|
@ -1,180 +0,0 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc (unknown)
|
||||
// source: store/activity.proto
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
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 ActivityMemoCommentPayload struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
MemoId int32 `protobuf:"varint,1,opt,name=memo_id,json=memoId,proto3" json:"memo_id,omitempty"`
|
||||
RelatedMemoId int32 `protobuf:"varint,2,opt,name=related_memo_id,json=relatedMemoId,proto3" json:"related_memo_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ActivityMemoCommentPayload) Reset() {
|
||||
*x = ActivityMemoCommentPayload{}
|
||||
mi := &file_store_activity_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ActivityMemoCommentPayload) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ActivityMemoCommentPayload) ProtoMessage() {}
|
||||
|
||||
func (x *ActivityMemoCommentPayload) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_store_activity_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 ActivityMemoCommentPayload.ProtoReflect.Descriptor instead.
|
||||
func (*ActivityMemoCommentPayload) Descriptor() ([]byte, []int) {
|
||||
return file_store_activity_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ActivityMemoCommentPayload) GetMemoId() int32 {
|
||||
if x != nil {
|
||||
return x.MemoId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ActivityMemoCommentPayload) GetRelatedMemoId() int32 {
|
||||
if x != nil {
|
||||
return x.RelatedMemoId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ActivityPayload struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
MemoComment *ActivityMemoCommentPayload `protobuf:"bytes,1,opt,name=memo_comment,json=memoComment,proto3" json:"memo_comment,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ActivityPayload) Reset() {
|
||||
*x = ActivityPayload{}
|
||||
mi := &file_store_activity_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ActivityPayload) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ActivityPayload) ProtoMessage() {}
|
||||
|
||||
func (x *ActivityPayload) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_store_activity_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 ActivityPayload.ProtoReflect.Descriptor instead.
|
||||
func (*ActivityPayload) Descriptor() ([]byte, []int) {
|
||||
return file_store_activity_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ActivityPayload) GetMemoComment() *ActivityMemoCommentPayload {
|
||||
if x != nil {
|
||||
return x.MemoComment
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_store_activity_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_store_activity_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x14store/activity.proto\x12\vmemos.store\"]\n" +
|
||||
"\x1aActivityMemoCommentPayload\x12\x17\n" +
|
||||
"\amemo_id\x18\x01 \x01(\x05R\x06memoId\x12&\n" +
|
||||
"\x0frelated_memo_id\x18\x02 \x01(\x05R\rrelatedMemoId\"]\n" +
|
||||
"\x0fActivityPayload\x12J\n" +
|
||||
"\fmemo_comment\x18\x01 \x01(\v2'.memos.store.ActivityMemoCommentPayloadR\vmemoCommentB\x98\x01\n" +
|
||||
"\x0fcom.memos.storeB\rActivityProtoP\x01Z)github.com/usememos/memos/proto/gen/store\xa2\x02\x03MSX\xaa\x02\vMemos.Store\xca\x02\vMemos\\Store\xe2\x02\x17Memos\\Store\\GPBMetadata\xea\x02\fMemos::Storeb\x06proto3"
|
||||
|
||||
var (
|
||||
file_store_activity_proto_rawDescOnce sync.Once
|
||||
file_store_activity_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_store_activity_proto_rawDescGZIP() []byte {
|
||||
file_store_activity_proto_rawDescOnce.Do(func() {
|
||||
file_store_activity_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_store_activity_proto_rawDesc), len(file_store_activity_proto_rawDesc)))
|
||||
})
|
||||
return file_store_activity_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_store_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_store_activity_proto_goTypes = []any{
|
||||
(*ActivityMemoCommentPayload)(nil), // 0: memos.store.ActivityMemoCommentPayload
|
||||
(*ActivityPayload)(nil), // 1: memos.store.ActivityPayload
|
||||
}
|
||||
var file_store_activity_proto_depIdxs = []int32{
|
||||
0, // 0: memos.store.ActivityPayload.memo_comment:type_name -> memos.store.ActivityMemoCommentPayload
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_store_activity_proto_init() }
|
||||
func file_store_activity_proto_init() {
|
||||
if File_store_activity_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_store_activity_proto_rawDesc), len(file_store_activity_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_store_activity_proto_goTypes,
|
||||
DependencyIndexes: file_store_activity_proto_depIdxs,
|
||||
MessageInfos: file_store_activity_proto_msgTypes,
|
||||
}.Build()
|
||||
File_store_activity_proto = out.File
|
||||
file_store_activity_proto_goTypes = nil
|
||||
file_store_activity_proto_depIdxs = nil
|
||||
}
|
||||
|
|
@ -72,8 +72,10 @@ type InboxMessage struct {
|
|||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// The type of the inbox message.
|
||||
Type InboxMessage_Type `protobuf:"varint,1,opt,name=type,proto3,enum=memos.store.InboxMessage_Type" json:"type,omitempty"`
|
||||
// The system-generated unique ID of related activity.
|
||||
ActivityId *int32 `protobuf:"varint,2,opt,name=activity_id,json=activityId,proto3,oneof" json:"activity_id,omitempty"`
|
||||
// Types that are valid to be assigned to Payload:
|
||||
//
|
||||
// *InboxMessage_MemoComment
|
||||
Payload isInboxMessage_Payload `protobuf_oneof:"payload"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
@ -115,9 +117,80 @@ func (x *InboxMessage) GetType() InboxMessage_Type {
|
|||
return InboxMessage_TYPE_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *InboxMessage) GetActivityId() int32 {
|
||||
if x != nil && x.ActivityId != nil {
|
||||
return *x.ActivityId
|
||||
func (x *InboxMessage) GetPayload() isInboxMessage_Payload {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *InboxMessage) GetMemoComment() *InboxMessage_MemoCommentPayload {
|
||||
if x != nil {
|
||||
if x, ok := x.Payload.(*InboxMessage_MemoComment); ok {
|
||||
return x.MemoComment
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isInboxMessage_Payload interface {
|
||||
isInboxMessage_Payload()
|
||||
}
|
||||
|
||||
type InboxMessage_MemoComment struct {
|
||||
MemoComment *InboxMessage_MemoCommentPayload `protobuf:"bytes,2,opt,name=memo_comment,json=memoComment,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*InboxMessage_MemoComment) isInboxMessage_Payload() {}
|
||||
|
||||
type InboxMessage_MemoCommentPayload struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
MemoId int32 `protobuf:"varint,1,opt,name=memo_id,json=memoId,proto3" json:"memo_id,omitempty"`
|
||||
RelatedMemoId int32 `protobuf:"varint,2,opt,name=related_memo_id,json=relatedMemoId,proto3" json:"related_memo_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *InboxMessage_MemoCommentPayload) Reset() {
|
||||
*x = InboxMessage_MemoCommentPayload{}
|
||||
mi := &file_store_inbox_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *InboxMessage_MemoCommentPayload) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*InboxMessage_MemoCommentPayload) ProtoMessage() {}
|
||||
|
||||
func (x *InboxMessage_MemoCommentPayload) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_store_inbox_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 InboxMessage_MemoCommentPayload.ProtoReflect.Descriptor instead.
|
||||
func (*InboxMessage_MemoCommentPayload) Descriptor() ([]byte, []int) {
|
||||
return file_store_inbox_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *InboxMessage_MemoCommentPayload) GetMemoId() int32 {
|
||||
if x != nil {
|
||||
return x.MemoId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *InboxMessage_MemoCommentPayload) GetRelatedMemoId() int32 {
|
||||
if x != nil {
|
||||
return x.RelatedMemoId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
@ -126,15 +199,17 @@ var File_store_inbox_proto protoreflect.FileDescriptor
|
|||
|
||||
const file_store_inbox_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x11store/inbox.proto\x12\vmemos.store\"\xa8\x01\n" +
|
||||
"\x11store/inbox.proto\x12\vmemos.store\"\xa7\x02\n" +
|
||||
"\fInboxMessage\x122\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2\x1e.memos.store.InboxMessage.TypeR\x04type\x12$\n" +
|
||||
"\vactivity_id\x18\x02 \x01(\x05H\x00R\n" +
|
||||
"activityId\x88\x01\x01\".\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2\x1e.memos.store.InboxMessage.TypeR\x04type\x12Q\n" +
|
||||
"\fmemo_comment\x18\x02 \x01(\v2,.memos.store.InboxMessage.MemoCommentPayloadH\x00R\vmemoComment\x1aU\n" +
|
||||
"\x12MemoCommentPayload\x12\x17\n" +
|
||||
"\amemo_id\x18\x01 \x01(\x05R\x06memoId\x12&\n" +
|
||||
"\x0frelated_memo_id\x18\x02 \x01(\x05R\rrelatedMemoId\".\n" +
|
||||
"\x04Type\x12\x14\n" +
|
||||
"\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" +
|
||||
"\fMEMO_COMMENT\x10\x01B\x0e\n" +
|
||||
"\f_activity_idB\x95\x01\n" +
|
||||
"\fMEMO_COMMENT\x10\x01B\t\n" +
|
||||
"\apayloadB\x95\x01\n" +
|
||||
"\x0fcom.memos.storeB\n" +
|
||||
"InboxProtoP\x01Z)github.com/usememos/memos/proto/gen/store\xa2\x02\x03MSX\xaa\x02\vMemos.Store\xca\x02\vMemos\\Store\xe2\x02\x17Memos\\Store\\GPBMetadata\xea\x02\fMemos::Storeb\x06proto3"
|
||||
|
||||
|
|
@ -151,18 +226,20 @@ func file_store_inbox_proto_rawDescGZIP() []byte {
|
|||
}
|
||||
|
||||
var file_store_inbox_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_store_inbox_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_store_inbox_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_store_inbox_proto_goTypes = []any{
|
||||
(InboxMessage_Type)(0), // 0: memos.store.InboxMessage.Type
|
||||
(*InboxMessage)(nil), // 1: memos.store.InboxMessage
|
||||
(InboxMessage_Type)(0), // 0: memos.store.InboxMessage.Type
|
||||
(*InboxMessage)(nil), // 1: memos.store.InboxMessage
|
||||
(*InboxMessage_MemoCommentPayload)(nil), // 2: memos.store.InboxMessage.MemoCommentPayload
|
||||
}
|
||||
var file_store_inbox_proto_depIdxs = []int32{
|
||||
0, // 0: memos.store.InboxMessage.type:type_name -> memos.store.InboxMessage.Type
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
2, // 1: memos.store.InboxMessage.memo_comment:type_name -> memos.store.InboxMessage.MemoCommentPayload
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_store_inbox_proto_init() }
|
||||
|
|
@ -170,14 +247,16 @@ func file_store_inbox_proto_init() {
|
|||
if File_store_inbox_proto != nil {
|
||||
return
|
||||
}
|
||||
file_store_inbox_proto_msgTypes[0].OneofWrappers = []any{}
|
||||
file_store_inbox_proto_msgTypes[0].OneofWrappers = []any{
|
||||
(*InboxMessage_MemoComment)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_store_inbox_proto_rawDesc), len(file_store_inbox_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 1,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package memos.store;
|
||||
|
||||
option go_package = "gen/store";
|
||||
|
||||
message ActivityMemoCommentPayload {
|
||||
int32 memo_id = 1;
|
||||
int32 related_memo_id = 2;
|
||||
}
|
||||
|
||||
message ActivityPayload {
|
||||
ActivityMemoCommentPayload memo_comment = 1;
|
||||
}
|
||||
|
|
@ -5,10 +5,16 @@ package memos.store;
|
|||
option go_package = "gen/store";
|
||||
|
||||
message InboxMessage {
|
||||
message MemoCommentPayload {
|
||||
int32 memo_id = 1;
|
||||
int32 related_memo_id = 2;
|
||||
}
|
||||
|
||||
// The type of the inbox message.
|
||||
Type type = 1;
|
||||
// The system-generated unique ID of related activity.
|
||||
optional int32 activity_id = 2;
|
||||
oneof payload {
|
||||
MemoCommentPayload memo_comment = 2;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
TYPE_UNSPECIFIED = 0;
|
||||
|
|
|
|||
|
|
@ -648,27 +648,18 @@ func (s *APIV1Service) CreateMemoComment(ctx context.Context, request *v1pb.Crea
|
|||
return nil, status.Errorf(codes.InvalidArgument, "invalid memo creator")
|
||||
}
|
||||
if memoComment.Visibility != v1pb.Visibility_PRIVATE && creatorID != relatedMemo.CreatorID {
|
||||
activity, err := s.Store.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: creatorID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{
|
||||
MemoComment: &storepb.ActivityMemoCommentPayload{
|
||||
MemoId: memo.ID,
|
||||
RelatedMemoId: relatedMemo.ID,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to create activity")
|
||||
}
|
||||
if _, err := s.Store.CreateInbox(ctx, &store.Inbox{
|
||||
SenderID: creatorID,
|
||||
ReceiverID: relatedMemo.CreatorID,
|
||||
Status: store.UNREAD,
|
||||
Message: &storepb.InboxMessage{
|
||||
Type: storepb.InboxMessage_MEMO_COMMENT,
|
||||
ActivityId: &activity.ID,
|
||||
Type: storepb.InboxMessage_MEMO_COMMENT,
|
||||
Payload: &storepb.InboxMessage_MemoComment{
|
||||
MemoComment: &storepb.InboxMessage_MemoCommentPayload{
|
||||
MemoId: memo.ID,
|
||||
RelatedMemoId: relatedMemo.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to create inbox")
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func TestListUserNotificationsIncludesMemoCommentPayload(t *testing.T) {
|
|||
require.Equal(t, memo.Name, notification.GetMemoComment().RelatedMemo)
|
||||
}
|
||||
|
||||
func TestListUserNotificationsOmitsPayloadWhenActivityMissing(t *testing.T) {
|
||||
func TestListUserNotificationsStoresMemoCommentPayloadInInbox(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
|
@ -93,17 +93,9 @@ func TestListUserNotificationsOmitsPayloadWhenActivityMissing(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Len(t, inboxes, 1)
|
||||
require.NotNil(t, inboxes[0].Message)
|
||||
require.NotNil(t, inboxes[0].Message.ActivityId)
|
||||
|
||||
_, err = ts.Store.GetDriver().GetDB().ExecContext(ctx, "DELETE FROM activity WHERE id = ?", *inboxes[0].Message.ActivityId)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := ts.Service.ListUserNotifications(ownerCtx, &apiv1.ListUserNotificationsRequest{
|
||||
Parent: fmt.Sprintf("users/%d", owner.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Notifications, 1)
|
||||
require.Nil(t, resp.Notifications[0].GetMemoComment())
|
||||
require.NotNil(t, inboxes[0].Message.GetMemoComment())
|
||||
require.NotZero(t, inboxes[0].Message.GetMemoComment().MemoId)
|
||||
require.NotZero(t, inboxes[0].Message.GetMemoComment().RelatedMemoId)
|
||||
}
|
||||
|
||||
func TestListUserNotificationsOmitsPayloadWhenMemosDeleted(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -1414,7 +1414,7 @@ func (s *APIV1Service) convertInboxToUserNotification(ctx context.Context, inbox
|
|||
notification.Status = v1pb.UserNotification_STATUS_UNSPECIFIED
|
||||
}
|
||||
|
||||
// Extract notification type and activity ID from inbox message
|
||||
// Extract notification type and payload from the inbox message.
|
||||
if inbox.Message != nil {
|
||||
switch inbox.Message.Type {
|
||||
case storepb.InboxMessage_MEMO_COMMENT:
|
||||
|
|
@ -1438,22 +1438,13 @@ func (s *APIV1Service) convertInboxToUserNotification(ctx context.Context, inbox
|
|||
}
|
||||
|
||||
func (s *APIV1Service) convertUserNotificationPayload(ctx context.Context, message *storepb.InboxMessage) (*v1pb.UserNotification_MemoCommentPayload, error) {
|
||||
if message == nil || message.Type != storepb.InboxMessage_MEMO_COMMENT || message.ActivityId == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
activity, err := s.Store.GetActivity(ctx, &store.FindActivity{
|
||||
ID: message.ActivityId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get activity")
|
||||
}
|
||||
if activity == nil || activity.Payload == nil || activity.Payload.MemoComment == nil {
|
||||
memoComment := message.GetMemoComment()
|
||||
if message == nil || message.Type != storepb.InboxMessage_MEMO_COMMENT || memoComment == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commentMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{
|
||||
ID: &activity.Payload.MemoComment.MemoId,
|
||||
ID: &memoComment.MemoId,
|
||||
ExcludeContent: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -1464,7 +1455,7 @@ func (s *APIV1Service) convertUserNotificationPayload(ctx context.Context, messa
|
|||
}
|
||||
|
||||
relatedMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{
|
||||
ID: &activity.Payload.MemoComment.RelatedMemoId,
|
||||
ID: &memoComment.RelatedMemoId,
|
||||
ExcludeContent: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
storepb "github.com/usememos/memos/proto/gen/store"
|
||||
)
|
||||
|
||||
type ActivityType string
|
||||
|
||||
const (
|
||||
ActivityTypeMemoComment ActivityType = "MEMO_COMMENT"
|
||||
)
|
||||
|
||||
func (t ActivityType) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
type ActivityLevel string
|
||||
|
||||
const (
|
||||
ActivityLevelInfo ActivityLevel = "INFO"
|
||||
)
|
||||
|
||||
func (l ActivityLevel) String() string {
|
||||
return string(l)
|
||||
}
|
||||
|
||||
type Activity struct {
|
||||
ID int32
|
||||
|
||||
// Standard fields
|
||||
CreatorID int32
|
||||
CreatedTs int64
|
||||
|
||||
// Domain specific fields
|
||||
Type ActivityType
|
||||
Level ActivityLevel
|
||||
Payload *storepb.ActivityPayload
|
||||
}
|
||||
|
||||
type FindActivity struct {
|
||||
ID *int32
|
||||
Type *ActivityType
|
||||
|
||||
// Pagination
|
||||
Limit *int
|
||||
Offset *int
|
||||
}
|
||||
|
||||
func (s *Store) CreateActivity(ctx context.Context, create *Activity) (*Activity, error) {
|
||||
return s.driver.CreateActivity(ctx, create)
|
||||
}
|
||||
|
||||
func (s *Store) ListActivities(ctx context.Context, find *FindActivity) ([]*Activity, error) {
|
||||
return s.driver.ListActivities(ctx, find)
|
||||
}
|
||||
|
||||
func (s *Store) GetActivity(ctx context.Context, find *FindActivity) (*Activity, error) {
|
||||
list, err := s.ListActivities(ctx, find)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return list[0], nil
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
storepb "github.com/usememos/memos/proto/gen/store"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
func (d *DB) CreateActivity(ctx context.Context, create *store.Activity) (*store.Activity, error) {
|
||||
payloadString := "{}"
|
||||
if create.Payload != nil {
|
||||
bytes, err := protojson.Marshal(create.Payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal activity payload")
|
||||
}
|
||||
payloadString = string(bytes)
|
||||
}
|
||||
fields := []string{"`creator_id`", "`type`", "`level`", "`payload`"}
|
||||
placeholder := []string{"?", "?", "?", "?"}
|
||||
args := []any{create.CreatorID, create.Type.String(), create.Level.String(), payloadString}
|
||||
|
||||
stmt := "INSERT INTO `activity` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")"
|
||||
result, err := d.db.ExecContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to execute statement")
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get last insert id")
|
||||
}
|
||||
|
||||
id32 := int32(id)
|
||||
|
||||
list, err := d.ListActivities(ctx, &store.FindActivity{ID: &id32})
|
||||
if err != nil || len(list) == 0 {
|
||||
return nil, errors.Wrap(err, "failed to find activity")
|
||||
}
|
||||
|
||||
return list[0], nil
|
||||
}
|
||||
|
||||
func (d *DB) ListActivities(ctx context.Context, find *store.FindActivity) ([]*store.Activity, error) {
|
||||
where, args := []string{"1 = 1"}, []any{}
|
||||
|
||||
if find.ID != nil {
|
||||
where, args = append(where, "`id` = ?"), append(args, *find.ID)
|
||||
}
|
||||
if find.Type != nil {
|
||||
where, args = append(where, "`type` = ?"), append(args, find.Type.String())
|
||||
}
|
||||
|
||||
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, UNIX_TIMESTAMP(`created_ts`) FROM `activity` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC"
|
||||
if find.Limit != nil {
|
||||
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
|
||||
if find.Offset != nil {
|
||||
query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := d.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
list := []*store.Activity{}
|
||||
for rows.Next() {
|
||||
activity := &store.Activity{}
|
||||
var payloadBytes []byte
|
||||
if err := rows.Scan(
|
||||
&activity.ID,
|
||||
&activity.CreatorID,
|
||||
&activity.Type,
|
||||
&activity.Level,
|
||||
&payloadBytes,
|
||||
&activity.CreatedTs,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := &storepb.ActivityPayload{}
|
||||
if err := protojsonUnmarshaler.Unmarshal(payloadBytes, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
activity.Payload = payload
|
||||
list = append(list, activity)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
storepb "github.com/usememos/memos/proto/gen/store"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
func (d *DB) CreateActivity(ctx context.Context, create *store.Activity) (*store.Activity, error) {
|
||||
payloadString := "{}"
|
||||
if create.Payload != nil {
|
||||
bytes, err := protojson.Marshal(create.Payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal activity payload")
|
||||
}
|
||||
payloadString = string(bytes)
|
||||
}
|
||||
|
||||
fields := []string{"creator_id", "type", "level", "payload"}
|
||||
args := []any{create.CreatorID, create.Type.String(), create.Level.String(), payloadString}
|
||||
stmt := "INSERT INTO activity (" + strings.Join(fields, ", ") + ") VALUES (" + placeholders(len(args)) + ") RETURNING id, created_ts"
|
||||
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
|
||||
&create.ID,
|
||||
&create.CreatedTs,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return create, nil
|
||||
}
|
||||
|
||||
func (d *DB) ListActivities(ctx context.Context, find *store.FindActivity) ([]*store.Activity, error) {
|
||||
where, args := []string{"1 = 1"}, []any{}
|
||||
if find.ID != nil {
|
||||
where, args = append(where, "id = "+placeholder(len(args)+1)), append(args, *find.ID)
|
||||
}
|
||||
if find.Type != nil {
|
||||
where, args = append(where, "type = "+placeholder(len(args)+1)), append(args, find.Type.String())
|
||||
}
|
||||
|
||||
query := "SELECT id, creator_id, type, level, payload, created_ts FROM activity WHERE " + strings.Join(where, " AND ") + " ORDER BY created_ts DESC"
|
||||
if find.Limit != nil {
|
||||
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
|
||||
if find.Offset != nil {
|
||||
query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := d.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
list := []*store.Activity{}
|
||||
for rows.Next() {
|
||||
activity := &store.Activity{}
|
||||
var payloadBytes []byte
|
||||
if err := rows.Scan(
|
||||
&activity.ID,
|
||||
&activity.CreatorID,
|
||||
&activity.Type,
|
||||
&activity.Level,
|
||||
&payloadBytes,
|
||||
&activity.CreatedTs,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := &storepb.ActivityPayload{}
|
||||
if err := protojsonUnmarshaler.Unmarshal(payloadBytes, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
activity.Payload = payload
|
||||
list = append(list, activity)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
storepb "github.com/usememos/memos/proto/gen/store"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
func (d *DB) CreateActivity(ctx context.Context, create *store.Activity) (*store.Activity, error) {
|
||||
payloadString := "{}"
|
||||
if create.Payload != nil {
|
||||
bytes, err := protojson.Marshal(create.Payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal activity payload")
|
||||
}
|
||||
payloadString = string(bytes)
|
||||
}
|
||||
|
||||
fields := []string{"`creator_id`", "`type`", "`level`", "`payload`"}
|
||||
placeholder := []string{"?", "?", "?", "?"}
|
||||
args := []any{create.CreatorID, create.Type.String(), create.Level.String(), payloadString}
|
||||
|
||||
stmt := "INSERT INTO activity (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`"
|
||||
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
|
||||
&create.ID,
|
||||
&create.CreatedTs,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return create, nil
|
||||
}
|
||||
|
||||
func (d *DB) ListActivities(ctx context.Context, find *store.FindActivity) ([]*store.Activity, error) {
|
||||
where, args := []string{"1 = 1"}, []any{}
|
||||
if find.ID != nil {
|
||||
where, args = append(where, "`id` = ?"), append(args, *find.ID)
|
||||
}
|
||||
if find.Type != nil {
|
||||
where, args = append(where, "`type` = ?"), append(args, find.Type.String())
|
||||
}
|
||||
|
||||
query := "SELECT `id`, `creator_id`, `type`, `level`, `payload`, `created_ts` FROM `activity` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC"
|
||||
if find.Limit != nil {
|
||||
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
|
||||
if find.Offset != nil {
|
||||
query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := d.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
list := []*store.Activity{}
|
||||
for rows.Next() {
|
||||
activity := &store.Activity{}
|
||||
var payloadBytes []byte
|
||||
if err := rows.Scan(
|
||||
&activity.ID,
|
||||
&activity.CreatorID,
|
||||
&activity.Type,
|
||||
&activity.Level,
|
||||
&payloadBytes,
|
||||
&activity.CreatedTs,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := &storepb.ActivityPayload{}
|
||||
if err := protojsonUnmarshaler.Unmarshal(payloadBytes, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
activity.Payload = payload
|
||||
list = append(list, activity)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
|
@ -13,10 +13,6 @@ type Driver interface {
|
|||
|
||||
IsInitialized(ctx context.Context) (bool, error)
|
||||
|
||||
// Activity model related methods.
|
||||
CreateActivity(ctx context.Context, create *Activity) (*Activity, error)
|
||||
ListActivities(ctx context.Context, find *FindActivity) ([]*Activity, error)
|
||||
|
||||
// Attachment model related methods.
|
||||
CreateAttachment(ctx context.Context, create *Attachment) (*Attachment, error)
|
||||
ListAttachments(ctx context.Context, find *FindAttachment) ([]*Attachment, error)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ func (s InboxStatus) String() string {
|
|||
}
|
||||
|
||||
// Inbox represents a notification in a user's inbox.
|
||||
// It connects activities to users who should be notified.
|
||||
type Inbox struct {
|
||||
ID int32
|
||||
CreatedTs int64
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
UPDATE `inbox` AS i
|
||||
JOIN `activity` AS a
|
||||
ON a.`id` = CAST(JSON_UNQUOTE(JSON_EXTRACT(i.`message`, '$.activityId')) AS UNSIGNED)
|
||||
SET i.`message` = JSON_SET(
|
||||
JSON_REMOVE(i.`message`, '$.activityId'),
|
||||
'$.memoComment',
|
||||
JSON_OBJECT(
|
||||
'memoId',
|
||||
JSON_EXTRACT(a.`payload`, '$.memoComment.memoId'),
|
||||
'relatedMemoId',
|
||||
JSON_EXTRACT(a.`payload`, '$.memoComment.relatedMemoId')
|
||||
)
|
||||
)
|
||||
WHERE JSON_EXTRACT(i.`message`, '$.activityId') IS NOT NULL;
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE `activity`;
|
||||
|
|
@ -67,16 +67,6 @@ CREATE TABLE `attachment` (
|
|||
`payload` TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- activity
|
||||
CREATE TABLE `activity` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`creator_id` INT NOT NULL,
|
||||
`created_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`type` VARCHAR(256) NOT NULL DEFAULT '',
|
||||
`level` VARCHAR(256) NOT NULL DEFAULT 'INFO',
|
||||
`payload` TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- idp
|
||||
CREATE TABLE `idp` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
UPDATE inbox AS i
|
||||
SET message = jsonb_set(
|
||||
i.message::jsonb - 'activityId',
|
||||
'{memoComment}',
|
||||
jsonb_build_object(
|
||||
'memoId',
|
||||
a.payload->'memoComment'->'memoId',
|
||||
'relatedMemoId',
|
||||
a.payload->'memoComment'->'relatedMemoId'
|
||||
)
|
||||
)::text
|
||||
FROM activity AS a
|
||||
WHERE (i.message::jsonb->>'activityId')::integer = a.id;
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE activity;
|
||||
|
|
@ -67,16 +67,6 @@ CREATE TABLE attachment (
|
|||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- activity
|
||||
CREATE TABLE activity (
|
||||
id SERIAL PRIMARY KEY,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
level TEXT NOT NULL DEFAULT 'INFO',
|
||||
payload JSONB NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- idp
|
||||
CREATE TABLE idp (
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
UPDATE inbox
|
||||
SET message = json_set(
|
||||
json_remove(message, '$.activityId'),
|
||||
'$.memoComment',
|
||||
json_object(
|
||||
'memoId',
|
||||
(
|
||||
SELECT json_extract(activity.payload, '$.memoComment.memoId')
|
||||
FROM activity
|
||||
WHERE activity.id = json_extract(inbox.message, '$.activityId')
|
||||
),
|
||||
'relatedMemoId',
|
||||
(
|
||||
SELECT json_extract(activity.payload, '$.memoComment.relatedMemoId')
|
||||
FROM activity
|
||||
WHERE activity.id = json_extract(inbox.message, '$.activityId')
|
||||
)
|
||||
)
|
||||
)
|
||||
WHERE json_extract(message, '$.activityId') IS NOT NULL
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM activity
|
||||
WHERE activity.id = json_extract(inbox.message, '$.activityId')
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE activity;
|
||||
|
|
@ -68,16 +68,6 @@ CREATE TABLE attachment (
|
|||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- activity
|
||||
CREATE TABLE activity (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- idp
|
||||
CREATE TABLE idp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
|
|
|||
|
|
@ -1,380 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
storepb "github.com/usememos/memos/proto/gen/store"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
func TestActivityStore(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
create := &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
}
|
||||
activity, err := ts.CreateActivity(ctx, create)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, activity)
|
||||
activities, err := ts.ListActivities(ctx, &store.FindActivity{
|
||||
ID: &activity.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(activities))
|
||||
require.Equal(t, activity, activities[0])
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityGetByID(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
activity, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get activity by ID
|
||||
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, found)
|
||||
require.Equal(t, activity.ID, found.ID)
|
||||
|
||||
// Get non-existent activity
|
||||
nonExistentID := int32(99999)
|
||||
notFound, err := ts.GetActivity(ctx, &store.FindActivity{ID: &nonExistentID})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, notFound)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityListMultiple(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create multiple activities
|
||||
_, err = ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// List all activities
|
||||
allActivities, err := ts.ListActivities(ctx, &store.FindActivity{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(allActivities))
|
||||
|
||||
// List by type
|
||||
commentType := store.ActivityTypeMemoComment
|
||||
commentActivities, err := ts.ListActivities(ctx, &store.FindActivity{Type: &commentType})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(commentActivities))
|
||||
require.Equal(t, store.ActivityTypeMemoComment, commentActivities[0].Type)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityListByType(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create activities with MEMO_COMMENT type
|
||||
_, err = ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// List by type
|
||||
activityType := store.ActivityTypeMemoComment
|
||||
activities, err := ts.ListActivities(ctx, &store.FindActivity{Type: &activityType})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 2)
|
||||
for _, activity := range activities {
|
||||
require.Equal(t, store.ActivityTypeMemoComment, activity.Type)
|
||||
}
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityPayloadMemoComment(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create activity with MemoComment payload
|
||||
memoID := int32(123)
|
||||
relatedMemoID := int32(456)
|
||||
activity, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{
|
||||
MemoComment: &storepb.ActivityMemoCommentPayload{
|
||||
MemoId: memoID,
|
||||
RelatedMemoId: relatedMemoID,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, activity.Payload)
|
||||
require.NotNil(t, activity.Payload.MemoComment)
|
||||
require.Equal(t, memoID, activity.Payload.MemoComment.MemoId)
|
||||
require.Equal(t, relatedMemoID, activity.Payload.MemoComment.RelatedMemoId)
|
||||
|
||||
// Verify payload is preserved when listing
|
||||
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, found.Payload.MemoComment)
|
||||
require.Equal(t, memoID, found.Payload.MemoComment.MemoId)
|
||||
require.Equal(t, relatedMemoID, found.Payload.MemoComment.RelatedMemoId)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityEmptyPayload(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create activity with empty payload
|
||||
activity, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, activity.Payload)
|
||||
|
||||
// Verify empty payload is handled correctly
|
||||
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, found.Payload)
|
||||
require.Nil(t, found.Payload.MemoComment)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityLevel(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create activity with INFO level
|
||||
activity, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, store.ActivityLevelInfo, activity.Level)
|
||||
|
||||
// Verify level is preserved when listing
|
||||
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, store.ActivityLevelInfo, found.Level)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityCreatorID(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user1, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
user2, err := createTestingUserWithRole(ctx, ts, "user2", store.RoleUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create activity for user1
|
||||
activity1, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user1.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, user1.ID, activity1.CreatorID)
|
||||
|
||||
// Create activity for user2
|
||||
activity2, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user2.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, user2.ID, activity2.CreatorID)
|
||||
|
||||
// List all and verify creator IDs
|
||||
activities, err := ts.ListActivities(ctx, &store.FindActivity{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 2)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityCreatedTs(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
activity, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, activity.CreatedTs)
|
||||
|
||||
// Verify timestamp is preserved when listing
|
||||
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, activity.CreatedTs, found.CreatedTs)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityListEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
|
||||
// List activities when none exist
|
||||
activities, err := ts.ListActivities(ctx, &store.FindActivity{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 0)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityListWithIDAndType(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
activity, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// List with both ID and Type filters
|
||||
activityType := store.ActivityTypeMemoComment
|
||||
activities, err := ts.ListActivities(ctx, &store.FindActivity{
|
||||
ID: &activity.ID,
|
||||
Type: &activityType,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 1)
|
||||
require.Equal(t, activity.ID, activities[0].ID)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
||||
func TestActivityPayloadComplexMemoComment(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a memo first to use its ID
|
||||
memo, err := ts.CreateMemo(ctx, &store.Memo{
|
||||
UID: "test-memo-for-activity",
|
||||
CreatorID: user.ID,
|
||||
Content: "Test memo content",
|
||||
Visibility: store.Public,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create comment memo
|
||||
commentMemo, err := ts.CreateMemo(ctx, &store.Memo{
|
||||
UID: "comment-memo",
|
||||
CreatorID: user.ID,
|
||||
Content: "This is a comment",
|
||||
Visibility: store.Public,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create activity with real memo IDs
|
||||
activity, err := ts.CreateActivity(ctx, &store.Activity{
|
||||
CreatorID: user.ID,
|
||||
Type: store.ActivityTypeMemoComment,
|
||||
Level: store.ActivityLevelInfo,
|
||||
Payload: &storepb.ActivityPayload{
|
||||
MemoComment: &storepb.ActivityMemoCommentPayload{
|
||||
MemoId: memo.ID,
|
||||
RelatedMemoId: commentMemo.ID,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, memo.ID, activity.Payload.MemoComment.MemoId)
|
||||
require.Equal(t, commentMemo.ID, activity.Payload.MemoComment.RelatedMemoId)
|
||||
|
||||
// Verify payload is preserved
|
||||
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, memo.ID, found.Payload.MemoComment.MemoId)
|
||||
require.Equal(t, commentMemo.ID, found.Payload.MemoComment.RelatedMemoId)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
|
@ -329,27 +329,37 @@ func TestInboxMessagePayload(t *testing.T) {
|
|||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create inbox with message payload containing activity ID
|
||||
activityID := int32(123)
|
||||
// Create inbox with message payload containing memo references.
|
||||
memoID := int32(123)
|
||||
relatedMemoID := int32(456)
|
||||
inbox, err := ts.CreateInbox(ctx, &store.Inbox{
|
||||
SenderID: 0,
|
||||
ReceiverID: user.ID,
|
||||
Status: store.UNREAD,
|
||||
Message: &storepb.InboxMessage{
|
||||
Type: storepb.InboxMessage_MEMO_COMMENT,
|
||||
ActivityId: &activityID,
|
||||
Type: storepb.InboxMessage_MEMO_COMMENT,
|
||||
Payload: &storepb.InboxMessage_MemoComment{
|
||||
MemoComment: &storepb.InboxMessage_MemoCommentPayload{
|
||||
MemoId: memoID,
|
||||
RelatedMemoId: relatedMemoID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, inbox.Message)
|
||||
require.Equal(t, storepb.InboxMessage_MEMO_COMMENT, inbox.Message.Type)
|
||||
require.Equal(t, activityID, *inbox.Message.ActivityId)
|
||||
require.NotNil(t, inbox.Message.GetMemoComment())
|
||||
require.Equal(t, memoID, inbox.Message.GetMemoComment().MemoId)
|
||||
require.Equal(t, relatedMemoID, inbox.Message.GetMemoComment().RelatedMemoId)
|
||||
|
||||
// List and verify payload is preserved
|
||||
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{ReceiverID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, inboxes, 1)
|
||||
require.Equal(t, activityID, *inboxes[0].Message.ActivityId)
|
||||
require.NotNil(t, inboxes[0].Message.GetMemoComment())
|
||||
require.Equal(t, memoID, inboxes[0].Message.GetMemoComment().MemoId)
|
||||
require.Equal(t, relatedMemoID, inboxes[0].Message.GetMemoComment().RelatedMemoId)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
|
@ -452,33 +462,43 @@ func TestInboxMessageTypeFilterWithPayload(t *testing.T) {
|
|||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create inbox with full payload
|
||||
activityID := int32(456)
|
||||
// Create inbox with full payload.
|
||||
memoID := int32(456)
|
||||
_, err = ts.CreateInbox(ctx, &store.Inbox{
|
||||
SenderID: 0,
|
||||
ReceiverID: user.ID,
|
||||
Status: store.UNREAD,
|
||||
Message: &storepb.InboxMessage{
|
||||
Type: storepb.InboxMessage_MEMO_COMMENT,
|
||||
ActivityId: &activityID,
|
||||
Type: storepb.InboxMessage_MEMO_COMMENT,
|
||||
Payload: &storepb.InboxMessage_MemoComment{
|
||||
MemoComment: &storepb.InboxMessage_MemoCommentPayload{
|
||||
MemoId: memoID,
|
||||
RelatedMemoId: 654,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create inbox with different type but also has payload
|
||||
otherActivityID := int32(789)
|
||||
// Create inbox with different type but also has payload.
|
||||
otherMemoID := int32(789)
|
||||
_, err = ts.CreateInbox(ctx, &store.Inbox{
|
||||
SenderID: 0,
|
||||
ReceiverID: user.ID,
|
||||
Status: store.UNREAD,
|
||||
Message: &storepb.InboxMessage{
|
||||
Type: storepb.InboxMessage_TYPE_UNSPECIFIED,
|
||||
ActivityId: &otherActivityID,
|
||||
Type: storepb.InboxMessage_TYPE_UNSPECIFIED,
|
||||
Payload: &storepb.InboxMessage_MemoComment{
|
||||
MemoComment: &storepb.InboxMessage_MemoCommentPayload{
|
||||
MemoId: otherMemoID,
|
||||
RelatedMemoId: 987,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Filter by type should work correctly even with complex JSON payload
|
||||
// Filter by type should work correctly even with complex JSON payload.
|
||||
memoCommentType := storepb.InboxMessage_MEMO_COMMENT
|
||||
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
|
||||
ReceiverID: &user.ID,
|
||||
|
|
@ -486,7 +506,8 @@ func TestInboxMessageTypeFilterWithPayload(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, inboxes, 1)
|
||||
require.Equal(t, activityID, *inboxes[0].Message.ActivityId)
|
||||
require.NotNil(t, inboxes[0].Message.GetMemoComment())
|
||||
require.Equal(t, memoID, inboxes[0].Message.GetMemoComment().MemoId)
|
||||
|
||||
ts.Close()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
storepb "github.com/usememos/memos/proto/gen/store"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
|
|
@ -87,6 +89,58 @@ func TestMigrationWithData(t *testing.T) {
|
|||
require.Equal(t, "Data before migration re-run", memo.Content, "memo content should be preserved")
|
||||
}
|
||||
|
||||
func TestMigrationBackfillsInboxMemoCommentPayload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
ts := NewTestingStore(ctx, t)
|
||||
|
||||
user, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
commentMemo, err := ts.CreateMemo(ctx, &store.Memo{
|
||||
UID: "migration-comment-memo",
|
||||
CreatorID: user.ID,
|
||||
Content: "Comment memo",
|
||||
Visibility: store.Public,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
relatedMemo, err := ts.CreateMemo(ctx, &store.Memo{
|
||||
UID: "migration-related-memo",
|
||||
CreatorID: user.ID,
|
||||
Content: "Related memo",
|
||||
Visibility: store.Public,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
driver := getDriverFromEnv()
|
||||
require.NoError(t, createLegacyActivityTable(ctx, ts, driver))
|
||||
require.NoError(t, insertLegacyInboxActivity(ctx, ts, driver, user.ID, commentMemo.ID, relatedMemo.ID))
|
||||
_, err = ts.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
||||
Key: storepb.InstanceSettingKey_BASIC,
|
||||
Value: &storepb.InstanceSetting_BasicSetting{
|
||||
BasicSetting: &storepb.InstanceBasicSetting{
|
||||
SchemaVersion: "0.27.2",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ts.Migrate(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
messageType := storepb.InboxMessage_MEMO_COMMENT
|
||||
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
|
||||
ReceiverID: &user.ID,
|
||||
MessageType: &messageType,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, inboxes, 1)
|
||||
require.NotNil(t, inboxes[0].Message)
|
||||
require.NotNil(t, inboxes[0].Message.GetMemoComment())
|
||||
require.Equal(t, commentMemo.ID, inboxes[0].Message.GetMemoComment().MemoId)
|
||||
require.Equal(t, relatedMemo.ID, inboxes[0].Message.GetMemoComment().RelatedMemoId)
|
||||
}
|
||||
|
||||
// TestMigrationMultipleReRuns verifies that migration is idempotent
|
||||
// even when run multiple times in succession.
|
||||
func TestMigrationMultipleReRuns(t *testing.T) {
|
||||
|
|
@ -111,6 +165,69 @@ func TestMigrationMultipleReRuns(t *testing.T) {
|
|||
require.Equal(t, initialVersion, finalVersion, "version should remain unchanged after multiple re-runs")
|
||||
}
|
||||
|
||||
func createLegacyActivityTable(ctx context.Context, ts *store.Store, driver string) error {
|
||||
var stmt string
|
||||
switch driver {
|
||||
case "sqlite":
|
||||
stmt = `CREATE TABLE activity (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
level TEXT NOT NULL DEFAULT 'INFO',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);`
|
||||
case "mysql":
|
||||
stmt = `CREATE TABLE activity (
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
creator_id INT NOT NULL,
|
||||
created_ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
type VARCHAR(256) NOT NULL DEFAULT '',
|
||||
level VARCHAR(256) NOT NULL DEFAULT 'INFO',
|
||||
payload TEXT NOT NULL
|
||||
);`
|
||||
case "postgres":
|
||||
stmt = `CREATE TABLE activity (
|
||||
id SERIAL PRIMARY KEY,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
level TEXT NOT NULL DEFAULT 'INFO',
|
||||
payload JSONB NOT NULL DEFAULT '{}'
|
||||
);`
|
||||
default:
|
||||
return errors.Errorf("unsupported driver: %s", driver)
|
||||
}
|
||||
_, err := ts.GetDriver().GetDB().ExecContext(ctx, stmt)
|
||||
return err
|
||||
}
|
||||
|
||||
func insertLegacyInboxActivity(ctx context.Context, ts *store.Store, driver string, receiverID, memoID, relatedMemoID int32) error {
|
||||
var insertActivityStmt string
|
||||
var insertInboxStmt string
|
||||
payload := fmt.Sprintf(`{"memoComment":{"memoId":%d,"relatedMemoId":%d}}`, memoID, relatedMemoID)
|
||||
message := `{"type":"MEMO_COMMENT","activityId":1}`
|
||||
|
||||
switch driver {
|
||||
case "sqlite", "mysql":
|
||||
insertActivityStmt = `INSERT INTO activity (id, creator_id, type, level, payload) VALUES (?, ?, ?, ?, ?)`
|
||||
insertInboxStmt = `INSERT INTO inbox (sender_id, receiver_id, status, message) VALUES (?, ?, ?, ?)`
|
||||
case "postgres":
|
||||
insertActivityStmt = `INSERT INTO activity (id, creator_id, type, level, payload) VALUES ($1, $2, $3, $4, $5::jsonb)`
|
||||
insertInboxStmt = `INSERT INTO inbox (sender_id, receiver_id, status, message) VALUES ($1, $2, $3, $4)`
|
||||
default:
|
||||
return errors.Errorf("unsupported driver: %s", driver)
|
||||
}
|
||||
|
||||
if _, err := ts.GetDriver().GetDB().ExecContext(ctx, insertActivityStmt, 1, receiverID, "MEMO_COMMENT", "INFO", payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := ts.GetDriver().GetDB().ExecContext(ctx, insertInboxStmt, receiverID, receiverID, store.UNREAD, message); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestMigrationFromStableVersion verifies that upgrading from a stable Memos version
|
||||
// to the current version works correctly. This is the critical upgrade path test.
|
||||
//
|
||||
|
|
|
|||
Loading…
Reference in New Issue