forked from 0x2E/fusion
201 lines
4.7 KiB
Go
201 lines
4.7 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"sync"
|
|
|
|
"github.com/0x2E/feedfinder"
|
|
"github.com/0x2e/fusion/model"
|
|
"github.com/0x2e/fusion/repo"
|
|
"github.com/0x2e/fusion/service/pull"
|
|
"github.com/0x2e/fusion/service/pull/client"
|
|
)
|
|
|
|
type FeedRepo interface {
|
|
List(filter *repo.FeedListFilter) ([]*model.Feed, error)
|
|
Get(id uint) (*model.Feed, error)
|
|
Create(feed []*model.Feed) error
|
|
Update(id uint, feed *model.Feed) error
|
|
Delete(id uint) error
|
|
}
|
|
|
|
type Feed struct {
|
|
repo FeedRepo
|
|
}
|
|
|
|
func NewFeed(repo FeedRepo) *Feed {
|
|
return &Feed{
|
|
repo: repo,
|
|
}
|
|
}
|
|
|
|
func (f Feed) List(ctx context.Context, req *ReqFeedList) (*RespFeedList, error) {
|
|
filter := &repo.FeedListFilter{
|
|
HaveUnread: req.HaveUnread,
|
|
HaveBookmark: req.HaveBookmark,
|
|
}
|
|
data, err := f.repo.List(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
feeds := make([]*FeedForm, 0, len(data))
|
|
for _, v := range data {
|
|
feeds = append(feeds, &FeedForm{
|
|
ID: v.ID,
|
|
Name: v.Name,
|
|
Link: v.Link,
|
|
Failure: v.Failure,
|
|
Suspended: v.Suspended,
|
|
ReqProxy: v.ReqProxy,
|
|
UpdatedAt: v.UpdatedAt,
|
|
UnreadCount: v.UnreadCount,
|
|
Group: GroupForm{ID: v.GroupID, Name: v.Group.Name},
|
|
})
|
|
}
|
|
return &RespFeedList{
|
|
Feeds: feeds,
|
|
}, nil
|
|
}
|
|
|
|
func (f Feed) Get(ctx context.Context, req *ReqFeedGet) (*RespFeedGet, error) {
|
|
data, err := f.repo.Get(req.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &RespFeedGet{
|
|
ID: data.ID,
|
|
Name: data.Name,
|
|
Link: data.Link,
|
|
Failure: data.Failure,
|
|
Suspended: data.Suspended,
|
|
ReqProxy: data.ReqProxy,
|
|
UpdatedAt: data.UpdatedAt,
|
|
Group: GroupForm{ID: data.GroupID, Name: data.Group.Name},
|
|
}, nil
|
|
}
|
|
|
|
func (f Feed) Create(ctx context.Context, req *ReqFeedCreate) (*RespFeedCreate, error) {
|
|
feeds := make([]*model.Feed, 0, len(req.Feeds))
|
|
for _, r := range req.Feeds {
|
|
feeds = append(feeds, &model.Feed{
|
|
Name: r.Name,
|
|
Link: r.Link,
|
|
FeedRequestOptions: model.FeedRequestOptions{
|
|
ReqProxy: r.RequestOptions.Proxy,
|
|
},
|
|
GroupID: req.GroupID,
|
|
})
|
|
}
|
|
|
|
if err := f.repo.Create(feeds); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// GORM assigns the ID to the model after Create
|
|
ids := make([]uint, 0, len(feeds))
|
|
for _, v := range feeds {
|
|
ids = append(ids, v.ID)
|
|
}
|
|
|
|
resp := &RespFeedCreate{
|
|
IDs: ids,
|
|
}
|
|
|
|
puller := pull.NewPuller(repo.NewFeed(repo.DB), repo.NewItem(repo.DB))
|
|
if len(feeds) > 1 {
|
|
go func() {
|
|
routinePool := make(chan struct{}, 10)
|
|
defer close(routinePool)
|
|
wg := sync.WaitGroup{}
|
|
for _, feed := range feeds {
|
|
routinePool <- struct{}{}
|
|
wg.Add(1)
|
|
go func() {
|
|
// NOTE: do not use the incoming ctx, as it will be Done() automatically
|
|
// by api timeout middleware
|
|
puller.PullOne(context.Background(), feed.ID)
|
|
<-routinePool
|
|
wg.Done()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
}()
|
|
return resp, nil
|
|
}
|
|
return resp, puller.PullOne(ctx, feeds[0].ID)
|
|
}
|
|
|
|
func (f Feed) CheckValidity(ctx context.Context, req *ReqFeedCheckValidity) (*RespFeedCheckValidity, error) {
|
|
if title, err := client.NewFeedClient().FetchTitle(ctx, req.Link, model.FeedRequestOptions{ReqProxy: req.RequestOptions.Proxy}); err == nil {
|
|
return &RespFeedCheckValidity{
|
|
FeedLinks: []ValidityItem{
|
|
{
|
|
Title: &title,
|
|
Link: &req.Link,
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
validLinks := make([]ValidityItem, 0)
|
|
target, err := url.Parse(req.Link)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sniffed, err := feedfinder.Find(ctx, target.String(), &feedfinder.Options{
|
|
RequestProxy: req.RequestOptions.Proxy,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, l := range sniffed {
|
|
validLinks = append(validLinks, ValidityItem{
|
|
Title: &l.Title,
|
|
Link: &l.Link,
|
|
})
|
|
}
|
|
return &RespFeedCheckValidity{
|
|
FeedLinks: validLinks,
|
|
}, nil
|
|
}
|
|
|
|
func (f Feed) Update(ctx context.Context, req *ReqFeedUpdate) error {
|
|
data := &model.Feed{
|
|
Name: req.Name,
|
|
Link: req.Link,
|
|
Suspended: req.Suspended,
|
|
FeedRequestOptions: model.FeedRequestOptions{
|
|
ReqProxy: req.ReqProxy,
|
|
},
|
|
}
|
|
if req.GroupID != nil {
|
|
data.GroupID = *req.GroupID
|
|
}
|
|
err := f.repo.Update(req.ID, data)
|
|
if errors.Is(err, repo.ErrDuplicatedKey) {
|
|
err = NewBizError(err, http.StatusBadRequest, "link is not allowed to be the same as other feeds")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (f Feed) Delete(ctx context.Context, req *ReqFeedDelete) error {
|
|
return f.repo.Delete(req.ID)
|
|
}
|
|
|
|
func (f Feed) Refresh(ctx context.Context, req *ReqFeedRefresh) error {
|
|
pull := pull.NewPuller(repo.NewFeed(repo.DB), repo.NewItem(repo.DB))
|
|
if req.ID != nil {
|
|
return pull.PullOne(ctx, *req.ID)
|
|
}
|
|
if req.All != nil && *req.All {
|
|
// NOTE: do not use the incoming ctx, as it will be Done() automatically
|
|
// by api timeout middleware
|
|
go pull.PullAll(context.Background(), true)
|
|
}
|
|
return nil
|
|
}
|