diff --git a/cmd/omnivore-as-rss/omnivore-as-rss.go b/cmd/omnivore-as-rss/omnivore-as-rss.go new file mode 100644 index 0000000..c03959c --- /dev/null +++ b/cmd/omnivore-as-rss/omnivore-as-rss.go @@ -0,0 +1,7 @@ +package main + +import "omnivore-as-rss/internal" + +func main() { + internal.Serve() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..33e4e2b --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module omnivore-as-rss + +go 1.19 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/feeds v1.1.2 // indirect + github.com/hasura/go-graphql-client v0.12.1 // indirect + golang.org/x/text v0.14.0 // indirect + nhooyr.io/websocket v1.8.10 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..032f11a --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw= +github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= +github.com/hasura/go-graphql-client v0.12.1 h1:tL+BCoyubkYYyaQ+tJz+oPe/pSxYwOJHwe5SSqqi6WI= +github.com/hasura/go-graphql-client v0.12.1/go.mod h1:F4N4kR6vY8amio3gEu3tjSZr8GPOXJr3zj72DKixfLE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= +nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/internal/feed.go b/internal/feed.go new file mode 100644 index 0000000..bd96493 --- /dev/null +++ b/internal/feed.go @@ -0,0 +1,40 @@ +package internal + +import ( + "github.com/gorilla/feeds" + "time" +) + +func generateFeed() (string, error) { + now := time.Now() + + feed := &feeds.Feed{ + Title: "Omnivore RSS", + Link: &feeds.Link{Href: "https://omnivore.app"}, + Description: "Automatically generated RSS feed based on the last items in your Omnivore account", + Created: now, + } + + items := []*feeds.Item{} + for _, item := range SearchQuery.Search.SearchSuccess.Edges { + + items = append(items, &feeds.Item{ + Id: item.Node.Id, + Title: item.Node.Title, + Created: item.Node.CreatedAt, + Author: &feeds.Author{ + Name: item.Node.Author, + Email: item.Node.Author, + }, + Link: &feeds.Link{ + Href: item.Node.Url, + }, + Description: item.Node.Description, + Content: item.Node.Content, + }) + } + + feed.Items = items + + return feed.ToRss() +} diff --git a/internal/graphql.go b/internal/graphql.go new file mode 100644 index 0000000..1fd33b6 --- /dev/null +++ b/internal/graphql.go @@ -0,0 +1,51 @@ +package internal + +import ( + "context" + "net/http" + "os" + "time" + "github.com/hasura/go-graphql-client" +) + +var client *graphql.Client + +func queryOmnivore() error { + if client == nil { + client = graphql.NewClient("https://api-prod.omnivore.app/api/graphql", nil).WithRequestModifier(func(r *http.Request) { + r.Header.Set("Authorization", os.Getenv("OMNIVORE_AUTH_TOKEN")) + }) + } + + err := client.Query(context.Background(), &SearchQuery, nil) + if err != nil { + return err + } + + return nil +} + +var SearchQuery struct { + Search struct { + SearchSuccess struct { + PageInfo struct { + TotalCount int + } + Edges []struct { + Node struct { + Id string + Title string + CreatedAt time.Time + Author string + Description string + Url string + Image string + Content string + } + } + } `graphql:"... on SearchSuccess"` + SearchError struct { + ErrorCodes []string + } `graphql:"... on SearchError"` + } `graphql:"search(first: 10, includeContent: true)"` +} diff --git a/internal/server.go b/internal/server.go new file mode 100644 index 0000000..97be59d --- /dev/null +++ b/internal/server.go @@ -0,0 +1,28 @@ +package internal + +import ( + "fmt" + "log" + "net/http" + +) + +func Serve() { + http.HandleFunc("/rss", rss) + log.Println("Starting web server: listening at port 8090") + http.ListenAndServe(":8090", nil) +} + +func rss(w http.ResponseWriter, req *http.Request) { + log.Println("Received request: Querying Omnivore...") + + feedString, err := getFeed() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Println(err) + return + } + + w.Header().Set("Content-Type", "application/rss+xml") + fmt.Fprintf(w, feedString) +} diff --git a/internal/service.go b/internal/service.go new file mode 100644 index 0000000..55c74d9 --- /dev/null +++ b/internal/service.go @@ -0,0 +1,25 @@ +package internal + +import ( + "errors" + "log" + "strings" +) + +func getFeed() (string, error) { + err := queryOmnivore() + + if err != nil { + log.Println("connection error on GraphQL Query: ", err) + return "", errors.New("connection to Omnivore error. See logs") + } + + // Check errorCodes from GraphQL API + errorCodes := SearchQuery.Search.SearchError.ErrorCodes + if len(errorCodes) != 0 { + errorCodesString := strings.Join(errorCodes[:], ",") + return "", errors.New("Errors returned from Omnivore GraphQL API: " + errorCodesString) + } + + return generateFeed() +}