diff --git a/api/v2/apidocs.swagger.yaml b/api/v2/apidocs.swagger.yaml index 8ea9eddd..ed7d7088 100644 --- a/api/v2/apidocs.swagger.yaml +++ b/api/v2/apidocs.swagger.yaml @@ -427,6 +427,7 @@ paths: - EYES - THINKING_FACE - CLOWN_FACE + - QUESTION_MARK default: TYPE_UNSPECIFIED tags: - MemoService @@ -1557,6 +1558,7 @@ definitions: - EYES - THINKING_FACE - CLOWN_FACE + - QUESTION_MARK default: TYPE_UNSPECIFIED apiv2RowStatus: type: string diff --git a/proto/api/v2/reaction_service.proto b/proto/api/v2/reaction_service.proto index aa9079eb..0ca68382 100644 --- a/proto/api/v2/reaction_service.proto +++ b/proto/api/v2/reaction_service.proto @@ -24,6 +24,7 @@ message Reaction { EYES = 9; THINKING_FACE = 10; CLOWN_FACE = 11; + QUESTION_MARK = 12; } Type reaction_type = 4; } diff --git a/proto/gen/api/v2/README.md b/proto/gen/api/v2/README.md index cb2de5ca..6a5dcc01 100644 --- a/proto/gen/api/v2/README.md +++ b/proto/gen/api/v2/README.md @@ -1182,6 +1182,7 @@ Used internally for obfuscating the page token. | EYES | 9 | | | THINKING_FACE | 10 | | | CLOWN_FACE | 11 | | +| QUESTION_MARK | 12 | | diff --git a/proto/gen/api/v2/reaction_service.pb.go b/proto/gen/api/v2/reaction_service.pb.go index 4175867d..62260efd 100644 --- a/proto/gen/api/v2/reaction_service.pb.go +++ b/proto/gen/api/v2/reaction_service.pb.go @@ -35,6 +35,7 @@ const ( Reaction_EYES Reaction_Type = 9 Reaction_THINKING_FACE Reaction_Type = 10 Reaction_CLOWN_FACE Reaction_Type = 11 + Reaction_QUESTION_MARK Reaction_Type = 12 ) // Enum value maps for Reaction_Type. @@ -52,6 +53,7 @@ var ( 9: "EYES", 10: "THINKING_FACE", 11: "CLOWN_FACE", + 12: "QUESTION_MARK", } Reaction_Type_value = map[string]int32{ "TYPE_UNSPECIFIED": 0, @@ -66,6 +68,7 @@ var ( "EYES": 9, "THINKING_FACE": 10, "CLOWN_FACE": 11, + "QUESTION_MARK": 12, } ) @@ -172,7 +175,7 @@ var File_api_v2_reaction_service_proto protoreflect.FileDescriptor var file_api_v2_reaction_service_proto_rawDesc = []byte{ 0x0a, 0x1d, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x0c, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x22, 0xce, 0x02, + 0x0c, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x22, 0xe1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x72, 0x65, @@ -182,7 +185,7 @@ var file_api_v2_reaction_service_proto_rawDesc = []byte{ 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xb6, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc9, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x48, 0x55, 0x4d, 0x42, 0x53, 0x5f, 0x55, 0x50, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x48, 0x55, 0x4d, 0x42, 0x53, 0x5f, 0x44, 0x4f, @@ -193,19 +196,20 @@ var file_api_v2_reaction_service_proto_rawDesc = []byte{ 0x41, 0x4e, 0x44, 0x10, 0x07, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x4f, 0x43, 0x4b, 0x45, 0x54, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x59, 0x45, 0x53, 0x10, 0x09, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x48, 0x49, 0x4e, 0x4b, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x43, 0x45, 0x10, 0x0a, 0x12, 0x0e, - 0x0a, 0x0a, 0x43, 0x4c, 0x4f, 0x57, 0x4e, 0x5f, 0x46, 0x41, 0x43, 0x45, 0x10, 0x0b, 0x42, 0xac, - 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x32, 0x42, 0x14, 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x73, 0x65, 0x6d, 0x65, 0x6d, 0x6f, 0x73, - 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x32, 0xa2, 0x02, 0x03, - 0x4d, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x70, 0x69, 0x2e, - 0x56, 0x32, 0xca, 0x02, 0x0c, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, - 0x32, 0xe2, 0x02, 0x18, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, - 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x4d, - 0x65, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x0a, 0x0a, 0x43, 0x4c, 0x4f, 0x57, 0x4e, 0x5f, 0x46, 0x41, 0x43, 0x45, 0x10, 0x0b, 0x12, 0x11, + 0x0a, 0x0d, 0x51, 0x55, 0x45, 0x53, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x41, 0x52, 0x4b, 0x10, + 0x0c, 0x42, 0xac, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x14, 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x73, 0x65, 0x6d, 0x65, + 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x32, + 0xa2, 0x02, 0x03, 0x4d, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x41, + 0x70, 0x69, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x0c, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x5c, 0x41, 0x70, + 0x69, 0x5c, 0x56, 0x32, 0xe2, 0x02, 0x18, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x5c, 0x41, 0x70, 0x69, + 0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x0e, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/gen/store/README.md b/proto/gen/store/README.md index 8179e061..b946a980 100644 --- a/proto/gen/store/README.md +++ b/proto/gen/store/README.md @@ -224,6 +224,7 @@ | EYES | 9 | | | THINKING_FACE | 10 | | | CLOWN_FACE | 11 | | +| QUESTION_MARK | 12 | | diff --git a/proto/gen/store/reaction.pb.go b/proto/gen/store/reaction.pb.go index 7e084172..af85af46 100644 --- a/proto/gen/store/reaction.pb.go +++ b/proto/gen/store/reaction.pb.go @@ -35,6 +35,7 @@ const ( Reaction_EYES Reaction_Type = 9 Reaction_THINKING_FACE Reaction_Type = 10 Reaction_CLOWN_FACE Reaction_Type = 11 + Reaction_QUESTION_MARK Reaction_Type = 12 ) // Enum value maps for Reaction_Type. @@ -52,6 +53,7 @@ var ( 9: "EYES", 10: "THINKING_FACE", 11: "CLOWN_FACE", + 12: "QUESTION_MARK", } Reaction_Type_value = map[string]int32{ "TYPE_UNSPECIFIED": 0, @@ -66,6 +68,7 @@ var ( "EYES": 9, "THINKING_FACE": 10, "CLOWN_FACE": 11, + "QUESTION_MARK": 12, } ) @@ -182,7 +185,7 @@ var File_store_reaction_proto protoreflect.FileDescriptor var file_store_reaction_proto_rawDesc = []byte{ 0x0a, 0x14, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x6f, 0x72, 0x65, 0x22, 0x84, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x54, 0x73, 0x12, @@ -193,7 +196,7 @@ var file_store_reaction_proto_rawDesc = []byte{ 0x0d, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x0c, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xb6, + 0x52, 0x0c, 0x72, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc9, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x48, 0x55, 0x4d, 0x42, 0x53, 0x5f, 0x55, 0x50, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, @@ -205,17 +208,18 @@ var file_store_reaction_proto_rawDesc = []byte{ 0x0a, 0x06, 0x52, 0x4f, 0x43, 0x4b, 0x45, 0x54, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x59, 0x45, 0x53, 0x10, 0x09, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x48, 0x49, 0x4e, 0x4b, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x43, 0x45, 0x10, 0x0a, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x4f, 0x57, 0x4e, - 0x5f, 0x46, 0x41, 0x43, 0x45, 0x10, 0x0b, 0x42, 0x98, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x0d, 0x52, 0x65, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x73, 0x65, 0x6d, 0x65, 0x6d, 0x6f, - 0x73, 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, - 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0xa2, 0x02, 0x03, 0x4d, 0x53, 0x58, 0xaa, 0x02, 0x0b, - 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xca, 0x02, 0x0b, 0x4d, 0x65, - 0x6d, 0x6f, 0x73, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xe2, 0x02, 0x17, 0x4d, 0x65, 0x6d, 0x6f, - 0x73, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x53, 0x74, 0x6f, - 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x5f, 0x46, 0x41, 0x43, 0x45, 0x10, 0x0b, 0x12, 0x11, 0x0a, 0x0d, 0x51, 0x55, 0x45, 0x53, 0x54, + 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x41, 0x52, 0x4b, 0x10, 0x0c, 0x42, 0x98, 0x01, 0x0a, 0x0f, 0x63, + 0x6f, 0x6d, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x0d, + 0x52, 0x65, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x73, 0x65, 0x6d, + 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0xa2, 0x02, 0x03, 0x4d, 0x53, 0x58, + 0xaa, 0x02, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xca, 0x02, + 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xe2, 0x02, 0x17, 0x4d, + 0x65, 0x6d, 0x6f, 0x73, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/store/reaction.proto b/proto/store/reaction.proto index 7de4c0a1..00cec419 100644 --- a/proto/store/reaction.proto +++ b/proto/store/reaction.proto @@ -28,6 +28,7 @@ message Reaction { EYES = 9; THINKING_FACE = 10; CLOWN_FACE = 11; + QUESTION_MARK = 12; } Type reaction_type = 5; } diff --git a/server/version/version.go b/server/version/version.go index f41aec8c..01eb1ce1 100644 --- a/server/version/version.go +++ b/server/version/version.go @@ -9,10 +9,10 @@ import ( // Version is the service current released version. // Semantic versioning: https://semver.org/ -var Version = "0.19.1" +var Version = "0.20.0" // DevVersion is the service current development version. -var DevVersion = "0.19.1" +var DevVersion = "0.20.0" func GetCurrentVersion(mode string) string { if mode == "dev" || mode == "demo" { diff --git a/store/db/mysql/migration/dev/LATEST__SCHEMA.sql b/store/db/mysql/migration/dev/LATEST__SCHEMA.sql index 7db3fe57..f69bbe30 100644 --- a/store/db/mysql/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/mysql/migration/dev/LATEST__SCHEMA.sql @@ -131,3 +131,13 @@ CREATE TABLE `webhook` ( `name` TEXT NOT NULL, `url` TEXT NOT NULL ); + +-- reaction +CREATE TABLE `reaction` ( + `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `created_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `creator_id` INT NOT NULL, + `content_id` VARCHAR(256) NOT NULL, + `reaction_type` VARCHAR(256) NOT NULL, + UNIQUE(`creator_id`,`content_id`,`reaction_type`) +); diff --git a/store/db/mysql/migration/prod/0.20/00__reaction.sql b/store/db/mysql/migration/prod/0.20/00__reaction.sql new file mode 100644 index 00000000..a25a1baf --- /dev/null +++ b/store/db/mysql/migration/prod/0.20/00__reaction.sql @@ -0,0 +1,9 @@ +-- reaction +CREATE TABLE `reaction` ( + `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `created_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `creator_id` INT NOT NULL, + `content_id` VARCHAR(256) NOT NULL, + `reaction_type` VARCHAR(256) NOT NULL, + UNIQUE(`creator_id`,`content_id`,`reaction_type`) +); diff --git a/store/db/mysql/migration/prod/LATEST__SCHEMA.sql b/store/db/mysql/migration/prod/LATEST__SCHEMA.sql index 7db3fe57..f69bbe30 100644 --- a/store/db/mysql/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/mysql/migration/prod/LATEST__SCHEMA.sql @@ -131,3 +131,13 @@ CREATE TABLE `webhook` ( `name` TEXT NOT NULL, `url` TEXT NOT NULL ); + +-- reaction +CREATE TABLE `reaction` ( + `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `created_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `creator_id` INT NOT NULL, + `content_id` VARCHAR(256) NOT NULL, + `reaction_type` VARCHAR(256) NOT NULL, + UNIQUE(`creator_id`,`content_id`,`reaction_type`) +); diff --git a/store/db/mysql/reaction.go b/store/db/mysql/reaction.go index 545cec2e..39702089 100644 --- a/store/db/mysql/reaction.go +++ b/store/db/mysql/reaction.go @@ -4,6 +4,7 @@ import ( "context" "strings" + "github.com/pkg/errors" storepb "github.com/usememos/memos/proto/gen/store" "github.com/usememos/memos/store" ) @@ -12,15 +13,24 @@ func (d *DB) UpsertReaction(ctx context.Context, upsert *storepb.Reaction) (*sto fields := []string{"`creator_id`", "`content_id`", "`reaction_type`"} placeholder := []string{"?", "?", "?"} args := []interface{}{upsert.CreatorId, upsert.ContentId, upsert.ReactionType.String()} - stmt := "INSERT INTO `reaction` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`" - if err := d.db.QueryRowContext(ctx, stmt, args...).Scan( - &upsert.Id, - &upsert.CreatedTs, - ); err != nil { + stmt := "INSERT INTO `reaction` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")" + result, err := d.db.ExecContext(ctx, stmt, args...) + if err != nil { return nil, err } - reaction := upsert + rawID, err := result.LastInsertId() + if err != nil { + return nil, err + } + id := int32(rawID) + reaction, err := d.GetReaction(ctx, &store.FindReaction{ID: &id}) + if err != nil { + return nil, err + } + if reaction == nil { + return nil, errors.Errorf("failed to create reaction") + } return reaction, nil } @@ -39,7 +49,7 @@ func (d *DB) ListReactions(ctx context.Context, find *store.FindReaction) ([]*st rows, err := d.db.QueryContext(ctx, ` SELECT id, - created_ts, + UNIX_TIMESTAMP(created_ts) AS created_ts, creator_id, content_id, reaction_type @@ -77,6 +87,19 @@ func (d *DB) ListReactions(ctx context.Context, find *store.FindReaction) ([]*st return list, nil } +func (d *DB) GetReaction(ctx context.Context, find *store.FindReaction) (*storepb.Reaction, error) { + list, err := d.ListReactions(ctx, find) + if err != nil { + return nil, err + } + if len(list) == 0 { + return nil, nil + } + + reaction := list[0] + return reaction, nil +} + func (d *DB) DeleteReaction(ctx context.Context, delete *store.DeleteReaction) error { _, err := d.db.ExecContext(ctx, "DELETE FROM `reaction` WHERE `id` = ?", delete.ID) return err diff --git a/store/db/postgres/migration/dev/LATEST__SCHEMA.sql b/store/db/postgres/migration/dev/LATEST__SCHEMA.sql index c0059ab3..1ab5f95e 100644 --- a/store/db/postgres/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/postgres/migration/dev/LATEST__SCHEMA.sql @@ -131,3 +131,13 @@ CREATE TABLE webhook ( name TEXT NOT NULL, url TEXT NOT NULL ); + +-- reaction +CREATE TABLE reaction ( + id SERIAL PRIMARY KEY, + created_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()), + creator_id INTEGER NOT NULL, + content_id TEXT NOT NULL, + reaction_type TEXT NOT NULL, + UNIQUE(creator_id, content_id, reaction_type) +); diff --git a/store/db/postgres/migration/prod/0.20/00_reaction.sql b/store/db/postgres/migration/prod/0.20/00_reaction.sql new file mode 100644 index 00000000..f9866ee9 --- /dev/null +++ b/store/db/postgres/migration/prod/0.20/00_reaction.sql @@ -0,0 +1,9 @@ +-- reaction +CREATE TABLE reaction ( + id SERIAL PRIMARY KEY, + created_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()), + creator_id INTEGER NOT NULL, + content_id TEXT NOT NULL, + reaction_type TEXT NOT NULL, + UNIQUE(creator_id, content_id, reaction_type) +); diff --git a/store/db/postgres/migration/prod/LATEST__SCHEMA.sql b/store/db/postgres/migration/prod/LATEST__SCHEMA.sql index c0059ab3..1ab5f95e 100644 --- a/store/db/postgres/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/postgres/migration/prod/LATEST__SCHEMA.sql @@ -131,3 +131,13 @@ CREATE TABLE webhook ( name TEXT NOT NULL, url TEXT NOT NULL ); + +-- reaction +CREATE TABLE reaction ( + id SERIAL PRIMARY KEY, + created_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()), + creator_id INTEGER NOT NULL, + content_id TEXT NOT NULL, + reaction_type TEXT NOT NULL, + UNIQUE(creator_id, content_id, reaction_type) +); diff --git a/store/db/sqlite/migration/dev/LATEST__SCHEMA.sql b/store/db/sqlite/migration/dev/LATEST__SCHEMA.sql index 13935ddb..b2408ddc 100644 --- a/store/db/sqlite/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/sqlite/migration/dev/LATEST__SCHEMA.sql @@ -145,6 +145,7 @@ CREATE TABLE webhook ( CREATE INDEX idx_webhook_creator_id ON webhook (creator_id); +-- reaction CREATE TABLE reaction ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), diff --git a/store/db/sqlite/migration/prod/0.20/00__reaction.sql b/store/db/sqlite/migration/prod/0.20/00__reaction.sql new file mode 100644 index 00000000..b4e8bd62 --- /dev/null +++ b/store/db/sqlite/migration/prod/0.20/00__reaction.sql @@ -0,0 +1,9 @@ +-- reaction +CREATE TABLE reaction ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), + creator_id INTEGER NOT NULL, + content_id TEXT NOT NULL, + reaction_type TEXT NOT NULL, + UNIQUE(creator_id, content_id, reaction_type) +); diff --git a/store/db/sqlite/migration/prod/LATEST__SCHEMA.sql b/store/db/sqlite/migration/prod/LATEST__SCHEMA.sql index 5ddce546..b2408ddc 100644 --- a/store/db/sqlite/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/sqlite/migration/prod/LATEST__SCHEMA.sql @@ -144,3 +144,13 @@ CREATE TABLE webhook ( ); CREATE INDEX idx_webhook_creator_id ON webhook (creator_id); + +-- reaction +CREATE TABLE reaction ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), + creator_id INTEGER NOT NULL, + content_id TEXT NOT NULL, + reaction_type TEXT NOT NULL, + UNIQUE(creator_id, content_id, reaction_type) +); diff --git a/test/store/store.go b/test/store/store.go index d3133cdd..ab642574 100644 --- a/test/store/store.go +++ b/test/store/store.go @@ -45,7 +45,8 @@ func resetTestingDB(ctx context.Context, profile *profile.Profile, dbDriver stor DROP TABLE IF EXISTS storage; DROP TABLE IF EXISTS idp; DROP TABLE IF EXISTS inbox; - DROP TABLE IF EXISTS webhook;`) + DROP TABLE IF EXISTS webhook; + DROP TABLE IF EXISTS reaction;`) if err != nil { fmt.Printf("failed to reset testing db, error: %+v\n", err) panic(err) @@ -65,7 +66,8 @@ func resetTestingDB(ctx context.Context, profile *profile.Profile, dbDriver stor DROP TABLE IF EXISTS storage CASCADE; DROP TABLE IF EXISTS idp CASCADE; DROP TABLE IF EXISTS inbox CASCADE; - DROP TABLE IF EXISTS webhook CASCADE;`) + DROP TABLE IF EXISTS webhook CASCADE; + DROP TABLE IF EXISTS reaction CASCADE;`) if err != nil { fmt.Printf("failed to reset testing db, error: %+v\n", err) panic(err) diff --git a/web/src/components/MemoActionMenu.tsx b/web/src/components/MemoActionMenu.tsx index 939002c2..9d99b364 100644 --- a/web/src/components/MemoActionMenu.tsx +++ b/web/src/components/MemoActionMenu.tsx @@ -116,7 +116,7 @@ const MemoActionMenu = (props: Props) => {
-
+
ID: {memo.name}
diff --git a/web/src/components/MemoView.tsx b/web/src/components/MemoView.tsx index 2e0aacb0..e5bd52a8 100644 --- a/web/src/components/MemoView.tsx +++ b/web/src/components/MemoView.tsx @@ -124,18 +124,16 @@ const MemoView: React.FC = (props: Props) => { )}
-
-
+
+
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && ( - <> - - - - - - + + + + + )} - {currentUser && memo.reactions.length === 0 && } + {currentUser && }
{!readonly && }
diff --git a/web/src/components/ReactionSelector.tsx b/web/src/components/ReactionSelector.tsx index eb76f250..abd46404 100644 --- a/web/src/components/ReactionSelector.tsx +++ b/web/src/components/ReactionSelector.tsx @@ -1,8 +1,10 @@ import { Dropdown, Menu, MenuButton } from "@mui/joy"; +import classNames from "classnames"; import { useRef, useState } from "react"; import useClickAway from "react-use/lib/useClickAway"; import Icon from "@/components/Icon"; import { memoServiceClient } from "@/grpcweb"; +import useCurrentUser from "@/hooks/useCurrentUser"; import { MemoNamePrefix, useMemoStore } from "@/store/v1"; import { Memo } from "@/types/proto/api/v2/memo_service"; import { Reaction_Type } from "@/types/proto/api/v2/reaction_service"; @@ -10,6 +12,7 @@ import { stringifyReactionType } from "./ReactionView"; interface Props { memo: Memo; + className?: string; } const REACTION_TYPES = [ @@ -24,10 +27,12 @@ const REACTION_TYPES = [ Reaction_Type.EYES, Reaction_Type.THINKING_FACE, Reaction_Type.CLOWN_FACE, + Reaction_Type.QUESTION_MARK, ]; const ReactionSelector = (props: Props) => { - const { memo } = props; + const { memo, className } = props; + const currentUser = useCurrentUser(); const memoStore = useMemoStore(); const [open, setOpen] = useState(false); const containerRef = useRef(null); @@ -36,15 +41,28 @@ const ReactionSelector = (props: Props) => { setOpen(false); }); - const handleReactionClick = async (reaction: Reaction_Type) => { + const hasReacted = (reactionType: Reaction_Type) => { + return memo.reactions.some((r) => r.reactionType === reactionType && r.creator === currentUser?.name); + }; + + const handleReactionClick = async (reactionType: Reaction_Type) => { try { - await memoServiceClient.upsertMemoReaction({ - id: memo.id, - reaction: { - contentId: `${MemoNamePrefix}${memo.id}`, - reactionType: reaction, - }, - }); + if (hasReacted(reactionType)) { + const reactions = memo.reactions.filter( + (reaction) => reaction.reactionType === reactionType && reaction.creator === currentUser.name, + ); + for (const reaction of reactions) { + await memoServiceClient.deleteMemoReaction({ id: reaction.id }); + } + } else { + await memoServiceClient.upsertMemoReaction({ + id: memo.id, + reaction: { + contentId: `${MemoNamePrefix}${memo.id}`, + reactionType: reactionType, + }, + }); + } await memoStore.getOrFetchMemoById(memo.id, { skipCache: true, }); @@ -57,7 +75,12 @@ const ReactionSelector = (props: Props) => { return ( setOpen(isOpen)}> - + @@ -68,7 +91,10 @@ const ReactionSelector = (props: Props) => { return ( handleReactionClick(reactionType)} > {stringifyReactionType(reactionType)} diff --git a/web/src/components/ReactionView.tsx b/web/src/components/ReactionView.tsx index 2c371716..41551eb4 100644 --- a/web/src/components/ReactionView.tsx +++ b/web/src/components/ReactionView.tsx @@ -37,6 +37,8 @@ export const stringifyReactionType = (reactionType: Reaction_Type): string => { return "🤔"; case Reaction_Type.CLOWN_FACE: return "🤡"; + case Reaction_Type.QUESTION_MARK: + return "❓"; default: return ""; } @@ -57,16 +59,16 @@ const stringifyUsers = (users: User[]): string => { const ReactionView = (props: Props) => { const { memo, reactionType, users } = props; - const currenUser = useCurrentUser(); + const currentUser = useCurrentUser(); const memoStore = useMemoStore(); - const hasReaction = users.some((user) => currenUser && user.username === currenUser.username); + const hasReaction = users.some((user) => currentUser && user.username === currentUser.username); const handleReactionClick = async () => { - if (!currenUser) { + if (!currentUser) { return; } - const index = users.findIndex((user) => user.username === currenUser.username); + const index = users.findIndex((user) => user.username === currentUser.username); try { if (index === -1) { await memoServiceClient.upsertMemoReaction({ @@ -78,7 +80,7 @@ const ReactionView = (props: Props) => { }); } else { const reactions = memo.reactions.filter( - (reaction) => reaction.reactionType === reactionType && reaction.creator === currenUser.name, + (reaction) => reaction.reactionType === reactionType && reaction.creator === currentUser.name, ); for (const reaction of reactions) { await memoServiceClient.deleteMemoReaction({ id: reaction.id }); @@ -95,7 +97,7 @@ const ReactionView = (props: Props) => {