/ journalistd / journalistd.go
journalistd.go
  1  package journalistd
  2  
  3  import (
  4  	"context"
  5  	"strconv"
  6  	"time"
  7  
  8  	"github.com/google/uuid"
  9  	"go.uber.org/zap"
 10  
 11  	"github.com/mrusme/journalist/ent"
 12  	"github.com/mrusme/journalist/ent/feed"
 13  	"github.com/mrusme/journalist/ent/item"
 14  	"github.com/mrusme/journalist/ent/user"
 15  
 16  	"github.com/mrusme/journalist/lib"
 17  	"github.com/mrusme/journalist/rss"
 18  )
 19  
 20  var VERSION string
 21  
 22  type Journalistd struct {
 23  	jctx *lib.JournalistContext
 24  
 25  	config    *lib.Config
 26  	entClient *ent.Client
 27  	logger    *zap.Logger
 28  
 29  	daemonStop          chan bool
 30  	autoRefreshInterval time.Duration
 31  }
 32  
 33  func Version() string {
 34  	return VERSION
 35  }
 36  
 37  func New(
 38  	jctx *lib.JournalistContext,
 39  ) (*Journalistd, error) {
 40  	jd := new(Journalistd)
 41  	jd.jctx = jctx
 42  	jd.config = jctx.Config
 43  	jd.entClient = jctx.EntClient
 44  	jd.logger = jctx.Logger
 45  
 46  	if err := jd.initAdminUser(); err != nil {
 47  		return nil, err
 48  	}
 49  
 50  	interval, err := strconv.Atoi(jd.config.Feeds.AutoRefresh)
 51  	if err != nil {
 52  		jd.logger.Error(
 53  			"Feeds.AutoRefresh is not a valid number (seconds)",
 54  			zap.Error(err),
 55  		)
 56  		return nil, err
 57  	}
 58  	jd.autoRefreshInterval = time.Duration(interval)
 59  
 60  	return jd, nil
 61  }
 62  
 63  func (jd *Journalistd) IsDebug() bool {
 64  	debug, err := strconv.ParseBool(jd.config.Debug)
 65  	if err != nil {
 66  		return false
 67  	}
 68  
 69  	return debug
 70  }
 71  
 72  func (jd *Journalistd) initAdminUser() error {
 73  	var admin *ent.User
 74  	var err error
 75  
 76  	admin, err = jd.entClient.User.
 77  		Query().
 78  		Where(user.Username(jd.config.Admin.Username)).
 79  		Only(context.Background())
 80  	if err != nil {
 81  		admin, err = jd.entClient.User.
 82  			Create().
 83  			SetUsername(jd.config.Admin.Username).
 84  			SetPassword(jd.config.Admin.Password).
 85  			SetRole("admin").
 86  			Save(context.Background())
 87  		if err != nil {
 88  			jd.logger.Error(
 89  				"Failed query/create admin user",
 90  				zap.Error(err),
 91  			)
 92  			return err
 93  		}
 94  	}
 95  
 96  	if admin.Password == "admin" {
 97  		jd.logger.Debug(
 98  			"Admin user",
 99  			zap.String("username", admin.Username),
100  			zap.String("password", admin.Password),
101  		)
102  	} else {
103  		jd.logger.Debug(
104  			"Admin user",
105  			zap.String("username", admin.Username),
106  			zap.String("password", "xxxxxx"),
107  		)
108  	}
109  
110  	return nil
111  }
112  
113  func (jd *Journalistd) Start() bool {
114  	jd.logger.Info(
115  		"Starting Journalist daemon",
116  	)
117  	jd.daemonStop = make(chan bool)
118  	go jd.daemon()
119  	return true
120  }
121  
122  func (jd *Journalistd) Stop() {
123  	jd.logger.Info(
124  		"Stopping Journalist daemon",
125  	)
126  	jd.daemonStop <- true
127  }
128  
129  func (jd *Journalistd) daemon() {
130  	jd.logger.Debug(
131  		"Journalist daemon started, looping",
132  	)
133  	for {
134  		select {
135  		case <-jd.daemonStop:
136  			jd.logger.Debug(
137  				"Journalist daemon loop ended",
138  			)
139  			return
140  		default:
141  			jd.logger.Debug(
142  				"RefreshAll starting, refreshing all feeds",
143  			)
144  			errs := jd.RefreshAll()
145  			if len(errs) > 0 {
146  				jd.logger.Error(
147  					"RefreshAll completed with errors",
148  					zap.Errors("errors", errs),
149  				)
150  			} else {
151  				jd.logger.Debug(
152  					"RefreshAll completed",
153  				)
154  			}
155  			time.Sleep(time.Second * jd.autoRefreshInterval)
156  		}
157  	}
158  }
159  
160  func (jd *Journalistd) RefreshAll() []error {
161  	var errs []error
162  
163  	dbFeeds, err := jd.entClient.Feed.
164  		Query().
165  		All(context.Background())
166  	if err != nil {
167  		errs = append(errs, err)
168  		return errs
169  	}
170  
171  	var feedIds []uuid.UUID = make([]uuid.UUID, len(dbFeeds))
172  	for i, dbFeed := range dbFeeds {
173  		feedIds[i] = dbFeed.ID
174  	}
175  
176  	return jd.Refresh(feedIds)
177  }
178  
179  func (jd *Journalistd) Refresh(feedIds []uuid.UUID) []error {
180  	var errs []error
181  
182  	dbFeeds, err := jd.entClient.Feed.
183  		Query().
184  		Where(
185  			feed.IDIn(feedIds...),
186  		).
187  		WithItems(func(query *ent.ItemQuery) {
188  			query.
189  				Select(item.FieldItemGUID).
190  				Where(item.CrawlerContentHTMLNEQ(""))
191  		}).
192  		All(context.Background())
193  	if err != nil {
194  		errs = append(errs, err)
195  		return errs
196  	}
197  
198  	for _, dbFeed := range dbFeeds {
199  		var exceptItemGUIDs []string
200  		for _, exceptItem := range dbFeed.Edges.Items {
201  			exceptItemGUIDs = append(exceptItemGUIDs, exceptItem.ItemGUID)
202  		}
203  
204  		rc, errr := rss.NewClient(
205  			dbFeed.URL,
206  			dbFeed.Username,
207  			dbFeed.Password,
208  			true,
209  			exceptItemGUIDs,
210  			jd.logger,
211  		)
212  		if len(errr) > 0 {
213  			errs = append(errs, errr...)
214  			continue
215  		}
216  
217  		dbFeedTmp := jd.entClient.Feed.
218  			Create()
219  		rc.SetFeed(
220  			dbFeed.URL,
221  			dbFeed.Username,
222  			dbFeed.Password,
223  			dbFeedTmp,
224  		)
225  		feedID, err := dbFeedTmp.
226  			OnConflictColumns("url", "username", "password").
227  			UpdateNewValues().
228  			ID(context.Background())
229  		if err != nil {
230  			errs = append(errs, err)
231  		}
232  
233  		dbItems := make([]*ent.ItemCreate, len(rc.Feed.Items))
234  		for i := 0; i < len(rc.Feed.Items); i++ {
235  			dbItems[i] = jd.entClient.Item.
236  				Create()
237  			dbItems[i] = rc.SetItem(
238  				feedID,
239  				i,
240  				dbItems[i],
241  			)
242  		}
243  		err = jd.entClient.Item.
244  			CreateBulk(dbItems...).
245  			OnConflictColumns("item_guid").
246  			UpdateNewValues().
247  			Exec(context.Background())
248  		if err != nil {
249  			errs = append(errs, err)
250  		}
251  	}
252  
253  	return errs
254  }