/
newshandler.go
136 lines (106 loc) · 3.38 KB
/
newshandler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package iago
import (
"encoding/json"
"errors"
"net/http"
"sync"
"github.com/sirupsen/logrus"
)
// Config defines settings of app. Currently, the embedded file provides details of the news sources.
type Config struct {
Sources []NewsSource `json:"sources"`
}
// NewsHandler defines the type which stores and handles the feed data.
type NewsHandler struct {
mutex *sync.Mutex
newsContent map[string][]story
}
// NewsSource defines the type which stores the details of a news source.
type NewsSource struct {
Url string `json:"url"`
ClientType string `json:"clientType"`
Topic string `json:"topic"`
EntryCount int `json:"entryCount"`
}
// feedContent represents the complete feed data from a single source.
type feedContent struct {
Title string
Article []story
}
// story represent the single entry in the feed. While RSS entries might not have an ID, other sources might provide
// IDs which might be useful in the future.
type story struct {
Id int64 `json:"id"`
Title string `json:"title"`
Url string `json:"url"`
}
// Init initializes the newsHandler struct.
func (n *NewsHandler) Init() {
n.mutex = &sync.Mutex{}
n.newsContent = make(map[string][]story)
}
// GetConfiguredSources gets the news sources from the config data.
func (n *NewsHandler) GetConfiguredSources(config string) []NewsSource {
var configData Config
err := json.Unmarshal([]byte(config), &configData)
if err != nil {
logrus.Error("unable to parse the config data. Error: ", err.Error())
}
sources := configData.Sources
if len(sources) == 0 {
logrus.Error("count of parsed sources is zero. Check the config.json.")
}
return sources
}
// UpdateFeed updates the news feed data content.
func (n *NewsHandler) UpdateFeed(s NewsSource) error {
readClient := n.getSourceHandler(s)
if readClient == nil {
return errors.New("unable to identify the client reader for the news source")
}
d, err := readClient.readData()
if err != nil {
logrus.Error("unable to fetch the from the news source. Error: " + err.Error())
return err
}
n.updateNewsFeed(d.Title, d.Article)
return nil
}
// HandleNewsRequests handles the incoming requests.
func (n *NewsHandler) HandleNewsRequests(w http.ResponseWriter, _ *http.Request) {
logrus.Info("received a request. Processing...")
data := n.generateNewsStream()
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.WriteHeader(http.StatusOK)
if wC, err := w.Write(data); err != nil {
logrus.Errorf("\nerror while writing response. error: %s, write count: %d", err.Error(), wC)
}
}
// generateNewsStream generates the data to be sent via HTTP from the news content map.
func (n *NewsHandler) generateNewsStream() []byte {
n.mutex.Lock()
data, err := json.Marshal(n.newsContent)
n.mutex.Unlock()
if nil != err {
logrus.Error("unable to marshal the json data. Error: " + err.Error())
return nil
}
return data
}
func (n *NewsHandler) getSourceHandler(s NewsSource) fetcher {
switch s.ClientType {
case "reddit":
return redditClient{name: s.Topic, url: s.Url, count: s.EntryCount}
case "rss":
return rssClient{name: s.Topic, url: s.Url, count: s.EntryCount}
case "hackernews":
return hnClient{name: s.Topic, count: s.EntryCount}
}
return nil
}
func (n *NewsHandler) updateNewsFeed(src string, data []story) {
n.mutex.Lock()
n.newsContent[src] = data
n.mutex.Unlock()
}