diff --git a/go.mod b/go.mod index 2166405..346ca9a 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.14 require ( github.com/Masterminds/semver/v3 v3.1.1 + github.com/PuerkitoBio/goquery v1.6.1 + github.com/SlyMarbo/rss v1.0.1 github.com/disintegration/imaging v1.6.2 github.com/getkin/kin-openapi v0.26.0 github.com/gojuno/go.osrm v0.1.1-0.20200217151037-435fc3e1d3d4 diff --git a/go.sum b/go.sum index f43cd68..88d18e6 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,12 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/hcsshim v0.8.7-0.20191101173118-65519b62243c/go.mod h1:7xhjOwRV2+0HXGmM0jxaEu+ZiXJFoVZOTfL/dmqbrD8= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= +github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk= +github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/SlyMarbo/rss v1.0.1 h1:fiaIU5UhcXauVOniHOIocWG7uj8Ej6pHNarMGPJilzA= +github.com/SlyMarbo/rss v1.0.1/go.mod h1:JNF+T33oj4m5WLCQXpBTCgO+SxRbYVgdiiimHNgzcbA= github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -62,10 +66,14 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= +github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -736,6 +744,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/rss/.gitignore b/rss/.gitignore new file mode 100644 index 0000000..b60d988 --- /dev/null +++ b/rss/.gitignore @@ -0,0 +1 @@ +rss diff --git a/rss/Dockerfile b/rss/Dockerfile new file mode 100644 index 0000000..a5b7e88 --- /dev/null +++ b/rss/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine +ADD rss /rss +ENTRYPOINT [ "/rss" ] diff --git a/rss/Makefile b/rss/Makefile new file mode 100644 index 0000000..c80cb4d --- /dev/null +++ b/rss/Makefile @@ -0,0 +1,26 @@ + +GOPATH:=$(shell go env GOPATH) +.PHONY: init +init: + go get -u github.com/golang/protobuf/proto + go get -u github.com/golang/protobuf/protoc-gen-go + go get github.com/micro/micro/v3/cmd/protoc-gen-micro +.PHONY: proto +proto: + protoc --proto_path=. --micro_out=. --go_out=:. proto/rss.proto + +.PHONY: api +api: + protoc --openapi_out=. --proto_path=. proto/rss.proto + +.PHONY: build +build: + go build -o rss *.go + +.PHONY: test +test: + go test -v ./... -cover + +.PHONY: docker +docker: + docker build . -t rss:latest diff --git a/rss/README.md b/rss/README.md new file mode 100644 index 0000000..f36c976 --- /dev/null +++ b/rss/README.md @@ -0,0 +1,6 @@ +RSS feed crawler and reader + +# RSS Service + +Provides a simple way to crawl and index RSS feeds making them accessibly via a simple unified API. + diff --git a/rss/generate.go b/rss/generate.go new file mode 100644 index 0000000..7d9db91 --- /dev/null +++ b/rss/generate.go @@ -0,0 +1,3 @@ +package main + +//go:generate make proto diff --git a/rss/handler/crawl.go b/rss/handler/crawl.go new file mode 100644 index 0000000..fa4e0b3 --- /dev/null +++ b/rss/handler/crawl.go @@ -0,0 +1,109 @@ +package handler + +import ( + "crypto/md5" + "fmt" + "net/url" + "sync" + "time" + + "github.com/SlyMarbo/rss" + log "github.com/micro/micro/v3/service/logger" + "github.com/micro/services/rss/parser" + pb "github.com/micro/services/rss/proto" +) + +var ( + rssSync sync.RWMutex + rssFeeds = map[string]*rss.Feed{} +) + +func (e *Rss) fetchAll() { + fs := []*pb.Feed{} + err := e.feeds.Read(e.feedsNameIndex.ToQuery(nil), &fs) + if err != nil { + log.Errorf("Error listing pb: %v", err) + return + } + if len(fs) == 0 { + log.Infof("No pb to fetch") + return + } + for _, feed := range fs { + err = e.fetch(feed) + if err != nil { + log.Errorf("Error saving post: %v", err) + } + } +} + +func (e *Rss) fetch(f *pb.Feed) error { + url := f.Url + log.Infof("Fetching address %v", url) + + // see if there's an existing rss feed + rssSync.RLock() + fd, ok := rssFeeds[f.Url] + rssSync.RUnlock() + + if !ok { + // create a new one if it doesn't exist + var err error + fd, err = rss.Fetch(f.Url) + if err != nil { + return fmt.Errorf("Error fetching address %v: %v", url, err) + } + // save the feed + rssSync.Lock() + rssFeeds[f.Url] = fd + rssSync.Unlock() + } else { + // otherwise update the existing feed + fd.Items = []*rss.Item{} + fd.Unread = 0 + if err := fd.Update(); err != nil { + return fmt.Errorf("Error updating address %v: %v", url, err) + } + } + + // set the refresh time + fd.Refresh = time.Now() + domain := getDomain(url) + + // range over the feed and save the items + for _, item := range fd.Items { + id := fmt.Sprintf("%x", md5.Sum([]byte(item.ID))) + + // check if content exists + content := item.Content + + // if we have a parser which returns content use it + // e.g cnbc + c, err := parser.Parse(item.Link) + if err == nil && len(c) > 0 { + content = c + } + + err = e.entries.Create(pb.Entry{ + Id: id, + Title: item.Title, + Summary: item.Summary, + Url: item.Link, + Domain: domain, + Content: content, + Date: item.Date.Unix(), + Category: f.Category, + }) + if err != nil { + return fmt.Errorf("Error saving item: %v", err) + } + + } + + return nil +} + +func getDomain(address string) string { + uri, _ := url.Parse(address) + return uri.Host +} diff --git a/rss/handler/rss.go b/rss/handler/rss.go new file mode 100644 index 0000000..65f782b --- /dev/null +++ b/rss/handler/rss.go @@ -0,0 +1,174 @@ +package handler + +import ( + "context" + "fmt" + "hash/fnv" + "strings" + "time" + + "github.com/micro/micro/v3/service/errors" + log "github.com/micro/micro/v3/service/logger" + "github.com/micro/micro/v3/service/model" + "github.com/micro/services/pkg/tenant" + pb "github.com/micro/services/rss/proto" +) + +type Rss struct { + feeds model.Model + entries model.Model + feedsIdIndex model.Index + feedsNameIndex model.Index + entriesDateIndex model.Index + entriesURLIndex model.Index +} + +func idFromName(name string) string { + hash := fnv.New64a() + hash.Write([]byte(name)) + return fmt.Sprintf("%d", hash.Sum64()) +} + +func NewRss() *Rss { + idIndex := model.ByEquality("id") + idIndex.Order.Type = model.OrderTypeUnordered + + nameIndex := model.ByEquality("name") + nameIndex.Order.Type = model.OrderTypeUnordered + + dateIndex := model.ByEquality("date") + dateIndex.Order.Type = model.OrderTypeDesc + + entriesURLIndex := model.ByEquality("url") + entriesURLIndex.Order.Type = model.OrderTypeDesc + entriesURLIndex.Order.FieldName = "date" + + f := &Rss{ + feeds: model.NewModel( + model.WithNamespace("feeds"), + model.WithIndexes(idIndex, nameIndex), + ), + entries: model.NewModel( + model.WithNamespace("entries"), + model.WithIndexes(dateIndex, entriesURLIndex), + ), + feedsIdIndex: idIndex, + feedsNameIndex: nameIndex, + entriesDateIndex: dateIndex, + entriesURLIndex: entriesURLIndex, + } + + // register model instances + f.feeds.Register(new(pb.Feed)) + f.entries.Register(new(pb.Entry)) + + go f.crawl() + return f +} + +func (e *Rss) crawl() { + e.fetchAll() + tick := time.NewTicker(1 * time.Minute) + for _ = range tick.C { + e.fetchAll() + } +} + +func (e *Rss) Add(ctx context.Context, req *pb.AddRequest, rsp *pb.AddResponse) error { + log.Info("Received Rss.Add request") + + if len(req.Name) == 0 { + return errors.BadRequest("rss.add", "require name") + } + + // get the tenantID + tenantID, ok := tenant.FromContext(ctx) + if !ok { + tenantID = "micro" + } + + f := pb.Feed{ + Id: tenantID + "/" + idFromName(req.Name), + Name: req.Name, + Url: req.Url, + Category: req.Category, + } + + // create the feed + e.feeds.Create(f) + + // schedule immediate fetch + go e.fetch(&f) + + return nil +} + +func (e *Rss) Feed(ctx context.Context, req *pb.FeedRequest, rsp *pb.FeedResponse) error { + log.Info("Received Rss.Entries request") + if len(req.Name) == 0 { + return errors.BadRequest("rss.feed", "missing feed name") + } + + // get the tenantID + tenantID, ok := tenant.FromContext(ctx) + if !ok { + tenantID = "micro" + } + + feed := new(pb.Feed) + id := tenantID + "/" + idFromName(req.Name) + q := model.QueryEquals("ID", id) + + // get the feed + if err := e.feeds.Read(q, feed); err != nil { + return errors.InternalServerError("rss.feeds", "could not read feed") + } + + // get the entries for each + return e.entries.Read(e.entriesURLIndex.ToQuery(feed.Url), &rsp.Entries) +} + +func (e *Rss) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error { + var feeds []*pb.Feed + q := model.QueryAll() + + // TODO: find a way to query only by tenant + err := e.feeds.Read(q, &feeds) + if err != nil { + return errors.InternalServerError("rss.list", "failed to read list of feeds: %v", err) + } + + // get the tenantID + tenantID, ok := tenant.FromContext(ctx) + if !ok { + tenantID = "micro" + } + + for _, feed := range feeds { + // filter for the tenant + if !strings.HasPrefix(feed.Id, tenantID+"/") { + continue + } + + rsp.Feeds = append(rsp.Feeds, feed) + } + + return nil +} + +func (e *Rss) Remove(ctx context.Context, req *pb.RemoveRequest, rsp *pb.RemoveResponse) error { + if len(req.Name) == 0 { + return errors.BadRequest("rss.remove", "blank name provided") + } + + // get the tenantID + tenantID, ok := tenant.FromContext(ctx) + if !ok { + tenantID = "micro" + } + + id := tenantID + "/" + idFromName(req.Name) + + e.feeds.Delete(model.QueryEquals("ID", id)) + return nil +} diff --git a/rss/main.go b/rss/main.go new file mode 100644 index 0000000..384ff24 --- /dev/null +++ b/rss/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/micro/micro/v3/service" + "github.com/micro/micro/v3/service/logger" + "github.com/micro/services/rss/handler" + pb "github.com/micro/services/rss/proto" +) + +func main() { + // Create service + srv := service.New( + service.Name("rss"), + ) + + // Register handler + pb.RegisterRssHandler(srv.Server(), handler.NewRss()) + + // Run service + if err := srv.Run(); err != nil { + logger.Fatal(err) + } +} diff --git a/rss/micro.mu b/rss/micro.mu new file mode 100644 index 0000000..be4c26c --- /dev/null +++ b/rss/micro.mu @@ -0,0 +1 @@ +service rss diff --git a/rss/parser/parser.go b/rss/parser/parser.go new file mode 100644 index 0000000..4842467 --- /dev/null +++ b/rss/parser/parser.go @@ -0,0 +1,63 @@ +package parser + +import ( + "errors" + "net/http" + "net/url" + + "github.com/PuerkitoBio/goquery" +) + +var ( + parsers = map[string]Parser{ + "a16z.com": a16zParser, + "cnbc.com": cnbcParser, + "www.cnbc.com": cnbcParser, + } +) + +type Parser func(string) (string, error) + +func Parse(uri string) (string, error) { + u, err := url.Parse(uri) + if err != nil { + return "", err + } + + if v, ok := parsers[u.Host]; ok { + return v(uri) + } + return "", errors.New("no parser for url") +} + +func classParser(class string) Parser { + return func(url string) (string, error) { + // Request the HTML page. + res, err := http.Get(url) + if err != nil { + return "", err + } + + defer res.Body.Close() + + if res.StatusCode != 200 { + return "", errors.New("bad status code") + } + + // Load the HTML document + doc, err := goquery.NewDocumentFromReader(res.Body) + if err != nil { + return "", err + } + + return doc.Find(class).Html() + } +} + +func a16zParser(url string) (string, error) { + return classParser(".blog-content")(url) +} + +func cnbcParser(url string) (string, error) { + return classParser(".PageBuilder-col-9")(url) +} diff --git a/rss/proto/rss.pb.go b/rss/proto/rss.pb.go new file mode 100644 index 0000000..e52936c --- /dev/null +++ b/rss/proto/rss.pb.go @@ -0,0 +1,821 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.6 +// source: proto/rss.proto + +package rss + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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 Feed struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // unique id + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // rss feed name + // eg. a16z + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // rss feed url + // eg. http://a16z.com/feed/ + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` + // category of the feed + Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` +} + +func (x *Feed) Reset() { + *x = Feed{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Feed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Feed) ProtoMessage() {} + +func (x *Feed) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_proto_msgTypes[0] + 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 Feed.ProtoReflect.Descriptor instead. +func (*Feed) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{0} +} + +func (x *Feed) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Feed) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Feed) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *Feed) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +type Entry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` + Summary string `protobuf:"bytes,5,opt,name=summary,proto3" json:"summary,omitempty"` + Content string `protobuf:"bytes,6,opt,name=content,proto3" json:"content,omitempty"` + Date int64 `protobuf:"varint,7,opt,name=date,proto3" json:"date,omitempty"` + Category string `protobuf:"bytes,8,opt,name=category,proto3" json:"category,omitempty"` +} + +func (x *Entry) Reset() { + *x = Entry{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Entry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Entry) ProtoMessage() {} + +func (x *Entry) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_proto_msgTypes[1] + 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 Entry.ProtoReflect.Descriptor instead. +func (*Entry) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{1} +} + +func (x *Entry) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Entry) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +func (x *Entry) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *Entry) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Entry) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + +func (x *Entry) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *Entry) GetDate() int64 { + if x != nil { + return x.Date + } + return 0 +} + +func (x *Entry) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +type AddRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // rss feed name + // eg. a16z + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // rss feed url + // eg. http://a16z.com/feed/ + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + // category to add + Category string `protobuf:"bytes,3,opt,name=category,proto3" json:"category,omitempty"` +} + +func (x *AddRequest) Reset() { + *x = AddRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddRequest) ProtoMessage() {} + +func (x *AddRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_proto_msgTypes[2] + 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 AddRequest.ProtoReflect.Descriptor instead. +func (*AddRequest) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{2} +} + +func (x *AddRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AddRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *AddRequest) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +type AddResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *AddResponse) Reset() { + *x = AddResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddResponse) ProtoMessage() {} + +func (x *AddResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_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 AddResponse.ProtoReflect.Descriptor instead. +func (*AddResponse) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{3} +} + +type FeedRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // rss feed name + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *FeedRequest) Reset() { + *x = FeedRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeedRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedRequest) ProtoMessage() {} + +func (x *FeedRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_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 FeedRequest.ProtoReflect.Descriptor instead. +func (*FeedRequest) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{4} +} + +func (x *FeedRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type FeedResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entries []*Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` +} + +func (x *FeedResponse) Reset() { + *x = FeedResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeedResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedResponse) ProtoMessage() {} + +func (x *FeedResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_proto_msgTypes[5] + 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 FeedResponse.ProtoReflect.Descriptor instead. +func (*FeedResponse) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{5} +} + +func (x *FeedResponse) GetEntries() []*Entry { + if x != nil { + return x.Entries + } + return nil +} + +type ListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListRequest) Reset() { + *x = ListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRequest) ProtoMessage() {} + +func (x *ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_proto_msgTypes[6] + 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 ListRequest.ProtoReflect.Descriptor instead. +func (*ListRequest) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{6} +} + +type ListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Feeds []*Feed `protobuf:"bytes,1,rep,name=feeds,proto3" json:"feeds,omitempty"` +} + +func (x *ListResponse) Reset() { + *x = ListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListResponse) ProtoMessage() {} + +func (x *ListResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_proto_msgTypes[7] + 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 ListResponse.ProtoReflect.Descriptor instead. +func (*ListResponse) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{7} +} + +func (x *ListResponse) GetFeeds() []*Feed { + if x != nil { + return x.Feeds + } + return nil +} + +type RemoveRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // rss feed name + // eg. a16z + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *RemoveRequest) Reset() { + *x = RemoveRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveRequest) ProtoMessage() {} + +func (x *RemoveRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_proto_msgTypes[8] + 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 RemoveRequest.ProtoReflect.Descriptor instead. +func (*RemoveRequest) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{8} +} + +func (x *RemoveRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type RemoveResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RemoveResponse) Reset() { + *x = RemoveResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_rss_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveResponse) ProtoMessage() {} + +func (x *RemoveResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_rss_proto_msgTypes[9] + 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 RemoveResponse.ProtoReflect.Descriptor instead. +func (*RemoveResponse) Descriptor() ([]byte, []int) { + return file_proto_rss_proto_rawDescGZIP(), []int{9} +} + +var File_proto_rss_proto protoreflect.FileDescriptor + +var file_proto_rss_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x03, 0x72, 0x73, 0x73, 0x22, 0x58, 0x0a, 0x04, 0x46, 0x65, 0x65, 0x64, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, + 0x22, 0xbb, 0x01, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x64, 0x61, + 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x4e, + 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, + 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x0d, + 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x0a, + 0x0b, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x22, 0x34, 0x0a, 0x0c, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x24, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0a, 0x2e, 0x72, 0x73, 0x73, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x66, 0x65, 0x65, 0x64, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x72, 0x73, 0x73, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x52, + 0x05, 0x66, 0x65, 0x65, 0x64, 0x73, 0x22, 0x23, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x10, 0x0a, 0x0e, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc4, 0x01, + 0x0a, 0x03, 0x52, 0x73, 0x73, 0x12, 0x2a, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x12, 0x0f, 0x2e, 0x72, + 0x73, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, + 0x72, 0x73, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x33, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x12, 0x2e, 0x72, 0x73, + 0x73, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x13, 0x2e, 0x72, 0x73, 0x73, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x04, 0x46, 0x65, 0x65, 0x64, 0x12, 0x10, + 0x2e, 0x72, 0x73, 0x73, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x11, 0x2e, 0x72, 0x73, 0x73, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x2e, + 0x72, 0x73, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x11, 0x2e, 0x72, 0x73, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, + 0x72, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_rss_proto_rawDescOnce sync.Once + file_proto_rss_proto_rawDescData = file_proto_rss_proto_rawDesc +) + +func file_proto_rss_proto_rawDescGZIP() []byte { + file_proto_rss_proto_rawDescOnce.Do(func() { + file_proto_rss_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_rss_proto_rawDescData) + }) + return file_proto_rss_proto_rawDescData +} + +var file_proto_rss_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_proto_rss_proto_goTypes = []interface{}{ + (*Feed)(nil), // 0: rss.Feed + (*Entry)(nil), // 1: rss.Entry + (*AddRequest)(nil), // 2: rss.AddRequest + (*AddResponse)(nil), // 3: rss.AddResponse + (*FeedRequest)(nil), // 4: rss.FeedRequest + (*FeedResponse)(nil), // 5: rss.FeedResponse + (*ListRequest)(nil), // 6: rss.ListRequest + (*ListResponse)(nil), // 7: rss.ListResponse + (*RemoveRequest)(nil), // 8: rss.RemoveRequest + (*RemoveResponse)(nil), // 9: rss.RemoveResponse +} +var file_proto_rss_proto_depIdxs = []int32{ + 1, // 0: rss.FeedResponse.entries:type_name -> rss.Entry + 0, // 1: rss.ListResponse.feeds:type_name -> rss.Feed + 2, // 2: rss.Rss.Add:input_type -> rss.AddRequest + 8, // 3: rss.Rss.Remove:input_type -> rss.RemoveRequest + 4, // 4: rss.Rss.Feed:input_type -> rss.FeedRequest + 6, // 5: rss.Rss.List:input_type -> rss.ListRequest + 3, // 6: rss.Rss.Add:output_type -> rss.AddResponse + 9, // 7: rss.Rss.Remove:output_type -> rss.RemoveResponse + 5, // 8: rss.Rss.Feed:output_type -> rss.FeedResponse + 7, // 9: rss.Rss.List:output_type -> rss.ListResponse + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] 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_rss_proto_init() } +func file_proto_rss_proto_init() { + if File_proto_rss_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_rss_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Feed); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Entry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeedRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeedResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_rss_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveResponse); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_rss_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_rss_proto_goTypes, + DependencyIndexes: file_proto_rss_proto_depIdxs, + MessageInfos: file_proto_rss_proto_msgTypes, + }.Build() + File_proto_rss_proto = out.File + file_proto_rss_proto_rawDesc = nil + file_proto_rss_proto_goTypes = nil + file_proto_rss_proto_depIdxs = nil +} diff --git a/rss/proto/rss.pb.micro.go b/rss/proto/rss.pb.micro.go new file mode 100644 index 0000000..0a4f734 --- /dev/null +++ b/rss/proto/rss.pb.micro.go @@ -0,0 +1,144 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: proto/rss.proto + +package rss + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +import ( + context "context" + api "github.com/micro/micro/v3/service/api" + client "github.com/micro/micro/v3/service/client" + server "github.com/micro/micro/v3/service/server" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// Reference imports to suppress errors if they are not otherwise used. +var _ api.Endpoint +var _ context.Context +var _ client.Option +var _ server.Option + +// Api Endpoints for Rss service + +func NewRssEndpoints() []*api.Endpoint { + return []*api.Endpoint{} +} + +// Client API for Rss service + +type RssService interface { + Add(ctx context.Context, in *AddRequest, opts ...client.CallOption) (*AddResponse, error) + Remove(ctx context.Context, in *RemoveRequest, opts ...client.CallOption) (*RemoveResponse, error) + Feed(ctx context.Context, in *FeedRequest, opts ...client.CallOption) (*FeedResponse, error) + List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) +} + +type rssService struct { + c client.Client + name string +} + +func NewRssService(name string, c client.Client) RssService { + return &rssService{ + c: c, + name: name, + } +} + +func (c *rssService) Add(ctx context.Context, in *AddRequest, opts ...client.CallOption) (*AddResponse, error) { + req := c.c.NewRequest(c.name, "Rss.Add", in) + out := new(AddResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *rssService) Remove(ctx context.Context, in *RemoveRequest, opts ...client.CallOption) (*RemoveResponse, error) { + req := c.c.NewRequest(c.name, "Rss.Remove", in) + out := new(RemoveResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *rssService) Feed(ctx context.Context, in *FeedRequest, opts ...client.CallOption) (*FeedResponse, error) { + req := c.c.NewRequest(c.name, "Rss.Feed", in) + out := new(FeedResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *rssService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) { + req := c.c.NewRequest(c.name, "Rss.List", in) + out := new(ListResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Rss service + +type RssHandler interface { + Add(context.Context, *AddRequest, *AddResponse) error + Remove(context.Context, *RemoveRequest, *RemoveResponse) error + Feed(context.Context, *FeedRequest, *FeedResponse) error + List(context.Context, *ListRequest, *ListResponse) error +} + +func RegisterRssHandler(s server.Server, hdlr RssHandler, opts ...server.HandlerOption) error { + type rss interface { + Add(ctx context.Context, in *AddRequest, out *AddResponse) error + Remove(ctx context.Context, in *RemoveRequest, out *RemoveResponse) error + Feed(ctx context.Context, in *FeedRequest, out *FeedResponse) error + List(ctx context.Context, in *ListRequest, out *ListResponse) error + } + type Rss struct { + rss + } + h := &rssHandler{hdlr} + return s.Handle(s.NewHandler(&Rss{h}, opts...)) +} + +type rssHandler struct { + RssHandler +} + +func (h *rssHandler) Add(ctx context.Context, in *AddRequest, out *AddResponse) error { + return h.RssHandler.Add(ctx, in, out) +} + +func (h *rssHandler) Remove(ctx context.Context, in *RemoveRequest, out *RemoveResponse) error { + return h.RssHandler.Remove(ctx, in, out) +} + +func (h *rssHandler) Feed(ctx context.Context, in *FeedRequest, out *FeedResponse) error { + return h.RssHandler.Feed(ctx, in, out) +} + +func (h *rssHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error { + return h.RssHandler.List(ctx, in, out) +} diff --git a/rss/proto/rss.proto b/rss/proto/rss.proto new file mode 100644 index 0000000..d6613e8 --- /dev/null +++ b/rss/proto/rss.proto @@ -0,0 +1,75 @@ +syntax = "proto3"; + +package rss; + +option go_package = "./proto;rss"; + +service Rss { + rpc Add(AddRequest) returns (AddResponse) {} + rpc Remove(RemoveRequest) returns (RemoveResponse) {} + rpc Feed(FeedRequest) returns (FeedResponse) {} + rpc List(ListRequest) returns (ListResponse) {} +} + +message Feed { + // unique id + string id = 1; + // rss feed name + // eg. a16z + string name = 2; + // rss feed url + // eg. http://a16z.com/feed/ + string url = 3; + // category of the feed + string category = 4; +} + +message Entry { + string id = 1; + string domain = 2; + string url = 3; + string title = 4; + string summary = 5; + string content = 6; + int64 date = 7; + string category = 8; +} + +message AddRequest { + // rss feed name + // eg. a16z + string name = 1; + // rss feed url + // eg. http://a16z.com/feed/ + string url = 2; + // category to add + string category = 3; +} + +message AddResponse { +} + +message FeedRequest { + // rss feed name + string name = 1; +} + +message FeedResponse { + repeated Entry entries = 1; +} + +message ListRequest {} + +message ListResponse { + repeated Feed feeds = 1; +} + +message RemoveRequest { + // rss feed name + // eg. a16z + string name = 1; +} + +message RemoveResponse { +} + diff --git a/rss/usage.md b/rss/usage.md new file mode 100644 index 0000000..f644533 --- /dev/null +++ b/rss/usage.md @@ -0,0 +1,56 @@ +A single uniform API for crawling and indexing RSS feeds + +# RSS Service + +Designed to populate the posts service with RSS feeds from other blogs. Useful for migration or just to get outside content into the posts service. + +## Creating a feed + +### cURL + +```bash +> curl 'https://api.m3o.com/rss/New' \ + -H 'micro-namespace: $yourNamespace' \ + -H 'authorization: Bearer $yourToken' \ + -d '{"name":"a16z", "url": "http://a16z.com/feed/"}'; +{} +``` + +### CLI + +```shell +micro rss add --name="a16z" --url=http://a16z.com/feed/ +``` + +## Querying feeded posts + +```shell +$ micro rss feed +{ + "entries": [ + { + "id": "39cdfbd6e7534bcd868be9eebbf43f8f", + "title": "Anthony Albanese: From the NYSE to Crypto", + "slug": "anthony-albanese-from-the-nyse-to-crypto", + "created": "1605104742", + "updated": "1605105364", + "metadata": { + "domain": "a16z.com", + "link": "https://a16z.com/2020/10/28/anthony-albanese-from-the-nyse-to-crypto/" + } + }, + { + "id": "5e9285c01311704e204322ba564cd99e", + "title": "Journal Club: From Insect Eyes to Nanomaterials", + "slug": "journal-club-from-insect-eyes-to-nanomaterials", + "created": "1605104741", + "updated": "1605105363", + "metadata": { + "domain": "a16z.com", + "link": "https://a16z.com/2020/10/29/journal-club-insect-eyes-nanomaterials/" + } + }, + ] +} +``` +