From 3181190e405c548100256a104f33ea39b423959c Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Wed, 15 Sep 2021 14:20:09 +0100 Subject: [PATCH] add search to twitter --- twitter/README.md | 4 +- twitter/handler/twitter.go | 32 ++++++ twitter/proto/twitter.pb.go | 180 +++++++++++++++++++++++++++--- twitter/proto/twitter.pb.micro.go | 17 +++ twitter/proto/twitter.proto | 14 +++ 5 files changed, 228 insertions(+), 19 deletions(-) diff --git a/twitter/README.md b/twitter/README.md index cb88ede..adb1845 100644 --- a/twitter/README.md +++ b/twitter/README.md @@ -1,6 +1,6 @@ -Simple twitter timeline +Realtime twitter timeline & search # Twitter Service -Retrieve the twitter timeline for a user without all the extra baggage +Retrieve the twitter timeline for a user or search for tweets without all the extra baggage of the existing twitter API. diff --git a/twitter/handler/twitter.go b/twitter/handler/twitter.go index 85d27bb..9d798e0 100644 --- a/twitter/handler/twitter.go +++ b/twitter/handler/twitter.go @@ -82,3 +82,35 @@ func (t *Twitter) Timeline(ctx context.Context, req *pb.TimelineRequest, rsp *pb return nil } + +func (t *Twitter) Search(ctx context.Context, req *pb.SearchRequest, rsp *pb.SearchResponse) error { + if len(req.Query) == 0 { + return errors.BadRequest("twitter.query", "missing query") + } + + if req.Limit <= 0 { + req.Limit = 20 + } + + searchRsp, _, err := t.Client.Search.Tweets(&twitter.SearchTweetParams{ + Query: req.Query, + Count: int(req.Limit), + }) + if err != nil { + logger.Errorf("Failed to retrieve tweets for %v: %v", req.Query, err) + return errors.InternalServerError("twitter.search", "Failed to retrieve tweets for %v: %v", req.Query, err) + } + + for _, tweet := range searchRsp.Statuses { + rsp.Tweets = append(rsp.Tweets, &pb.Tweet{ + Id: tweet.ID, + Text: tweet.Text, + CreatedAt: tweet.CreatedAt, + FavouritedCount: int64(tweet.FavoriteCount), + RetweetedCount: int64(tweet.RetweetCount), + Username: tweet.User.ScreenName, + }) + } + + return nil +} diff --git a/twitter/proto/twitter.pb.go b/twitter/proto/twitter.pb.go index 137f126..e31195f 100644 --- a/twitter/proto/twitter.pb.go +++ b/twitter/proto/twitter.pb.go @@ -219,6 +219,112 @@ func (x *TimelineResponse) GetTweets() []*Tweet { return nil } +// Search for tweets with a simple query +type SearchRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // the query to search for + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` + // number of tweets to return. default: 20 + Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` +} + +func (x *SearchRequest) Reset() { + *x = SearchRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_twitter_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchRequest) ProtoMessage() {} + +func (x *SearchRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_twitter_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchRequest.ProtoReflect.Descriptor instead. +func (*SearchRequest) Descriptor() ([]byte, []int) { + return file_proto_twitter_proto_rawDescGZIP(), []int{3} +} + +func (x *SearchRequest) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +func (x *SearchRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +type SearchResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // the related tweets for the search + Tweets []*Tweet `protobuf:"bytes,2,rep,name=tweets,proto3" json:"tweets,omitempty"` +} + +func (x *SearchResponse) Reset() { + *x = SearchResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_twitter_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchResponse) ProtoMessage() {} + +func (x *SearchResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_twitter_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchResponse.ProtoReflect.Descriptor instead. +func (*SearchResponse) Descriptor() ([]byte, []int) { + return file_proto_twitter_proto_rawDescGZIP(), []int{4} +} + +func (x *SearchResponse) GetTweets() []*Tweet { + if x != nil { + return x.Tweets + } + return nil +} + var File_proto_twitter_proto protoreflect.FileDescriptor var file_proto_twitter_proto_rawDesc = []byte{ @@ -243,14 +349,25 @@ var file_proto_twitter_proto_rawDesc = []byte{ 0x22, 0x3a, 0x0a, 0x10, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x74, 0x77, 0x65, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x54, - 0x77, 0x65, 0x65, 0x74, 0x52, 0x06, 0x74, 0x77, 0x65, 0x65, 0x74, 0x73, 0x32, 0x4c, 0x0a, 0x07, - 0x54, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x6c, - 0x69, 0x6e, 0x65, 0x12, 0x18, 0x2e, 0x74, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x74, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f, 0x2e, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x74, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x77, 0x65, 0x65, 0x74, 0x52, 0x06, 0x74, 0x77, 0x65, 0x65, 0x74, 0x73, 0x22, 0x3b, 0x0a, 0x0d, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x38, 0x0a, 0x0e, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x74, + 0x77, 0x65, 0x65, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x77, + 0x69, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x54, 0x77, 0x65, 0x65, 0x74, 0x52, 0x06, 0x74, 0x77, 0x65, + 0x65, 0x74, 0x73, 0x32, 0x89, 0x01, 0x0a, 0x07, 0x54, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x12, + 0x41, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x18, 0x2e, 0x74, 0x77, + 0x69, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x74, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x74, + 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x11, 0x5a, 0x0f, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x74, 0x77, 0x69, 0x74, 0x74, + 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -265,21 +382,26 @@ func file_proto_twitter_proto_rawDescGZIP() []byte { return file_proto_twitter_proto_rawDescData } -var file_proto_twitter_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_proto_twitter_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_proto_twitter_proto_goTypes = []interface{}{ (*Tweet)(nil), // 0: twitter.Tweet (*TimelineRequest)(nil), // 1: twitter.TimelineRequest (*TimelineResponse)(nil), // 2: twitter.TimelineResponse + (*SearchRequest)(nil), // 3: twitter.SearchRequest + (*SearchResponse)(nil), // 4: twitter.SearchResponse } var file_proto_twitter_proto_depIdxs = []int32{ 0, // 0: twitter.TimelineResponse.tweets:type_name -> twitter.Tweet - 1, // 1: twitter.Twitter.Timeline:input_type -> twitter.TimelineRequest - 2, // 2: twitter.Twitter.Timeline:output_type -> twitter.TimelineResponse - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] 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 + 0, // 1: twitter.SearchResponse.tweets:type_name -> twitter.Tweet + 1, // 2: twitter.Twitter.Timeline:input_type -> twitter.TimelineRequest + 3, // 3: twitter.Twitter.Search:input_type -> twitter.SearchRequest + 2, // 4: twitter.Twitter.Timeline:output_type -> twitter.TimelineResponse + 4, // 5: twitter.Twitter.Search:output_type -> twitter.SearchResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] 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_proto_twitter_proto_init() } @@ -324,6 +446,30 @@ func file_proto_twitter_proto_init() { return nil } } + file_proto_twitter_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SearchRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_twitter_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SearchResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -331,7 +477,7 @@ func file_proto_twitter_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_twitter_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/twitter/proto/twitter.pb.micro.go b/twitter/proto/twitter.pb.micro.go index 0af7f40..2d9f7d9 100644 --- a/twitter/proto/twitter.pb.micro.go +++ b/twitter/proto/twitter.pb.micro.go @@ -43,6 +43,7 @@ func NewTwitterEndpoints() []*api.Endpoint { type TwitterService interface { Timeline(ctx context.Context, in *TimelineRequest, opts ...client.CallOption) (*TimelineResponse, error) + Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error) } type twitterService struct { @@ -67,15 +68,27 @@ func (c *twitterService) Timeline(ctx context.Context, in *TimelineRequest, opts return out, nil } +func (c *twitterService) Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error) { + req := c.c.NewRequest(c.name, "Twitter.Search", in) + out := new(SearchResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Twitter service type TwitterHandler interface { Timeline(context.Context, *TimelineRequest, *TimelineResponse) error + Search(context.Context, *SearchRequest, *SearchResponse) error } func RegisterTwitterHandler(s server.Server, hdlr TwitterHandler, opts ...server.HandlerOption) error { type twitter interface { Timeline(ctx context.Context, in *TimelineRequest, out *TimelineResponse) error + Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error } type Twitter struct { twitter @@ -91,3 +104,7 @@ type twitterHandler struct { func (h *twitterHandler) Timeline(ctx context.Context, in *TimelineRequest, out *TimelineResponse) error { return h.TwitterHandler.Timeline(ctx, in, out) } + +func (h *twitterHandler) Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error { + return h.TwitterHandler.Search(ctx, in, out) +} diff --git a/twitter/proto/twitter.proto b/twitter/proto/twitter.proto index 584b1ca..86f45dc 100644 --- a/twitter/proto/twitter.proto +++ b/twitter/proto/twitter.proto @@ -6,6 +6,7 @@ option go_package = "./proto;twitter"; service Twitter { rpc Timeline(TimelineRequest) returns (TimelineResponse) {} + rpc Search(SearchRequest) returns (SearchResponse) {} } message Tweet { @@ -35,3 +36,16 @@ message TimelineResponse { // The recent tweets for the user repeated Tweet tweets = 1; } + +// Search for tweets with a simple query +message SearchRequest { + // the query to search for + string query = 1; + // number of tweets to return. default: 20 + int32 limit = 2; +} + +message SearchResponse { + // the related tweets for the search + repeated Tweet tweets = 2; +}