package posts import ( "bytes" "context" "github.com/PuerkitoBio/goquery" "github.com/Southclaws/fault" "html/template" "lmika.dev/lmika/hugo-cms/models" "lmika.dev/lmika/hugo-cms/providers/db" "lmika.dev/lmika/hugo-cms/services/jobs" "lmika.dev/lmika/hugo-cms/services/sitebuilder" "resty.dev/v3" "time" ) type Service struct { db *db.DB sb *sitebuilder.Service jobs *jobs.Service } func New( db *db.DB, sb *sitebuilder.Service, jobs *jobs.Service, ) *Service { return &Service{ db: db, sb: sb, jobs: jobs, } } func (s *Service) ListPostOfSite(ctx context.Context, site models.Site) ([]models.Post, error) { return s.db.ListPostsOfSite(ctx, site.ID) } func (s *Service) GetPost(ctx context.Context, id int) (models.Post, error) { post, err := s.db.GetPost(ctx, int64(id)) if err != nil { return models.Post{}, err } return post, nil } func (s *Service) DeletePost(ctx context.Context, site models.Site, id int) error { post, err := s.db.GetPost(ctx, int64(id)) if err != nil { return err } if err := s.db.DeletePost(ctx, int64(id)); err != nil { return err } return s.jobs.Queue(ctx, s.sb.DeletePost(site, post)) } func (s *Service) Create(ctx context.Context, site models.Site, req NewPost) (models.Post, error) { post := models.Post{ SiteID: site.ID, Title: req.Title, Body: req.Body, State: models.PostStatePublished, PublishDate: time.Now(), } if err := s.Save(ctx, site, &post); err != nil { return models.Post{}, err } return post, nil } func (s *Service) CreateLinkPost(ctx context.Context, site models.Site, req NewLinkPost) (models.Post, error) { fl, err := s.fetchLinkedPage(ctx, req) if err != nil { return models.Post{}, err } var bodyTemplate bytes.Buffer if err := linkedPostTemplate.Execute(&bodyTemplate, fl); err != nil { return models.Post{}, err } post := models.Post{ SiteID: site.ID, Title: fl.LinkTitle, Body: bodyTemplate.String(), } return post, nil } func (s *Service) Save(ctx context.Context, site models.Site, post *models.Post) error { post.SiteID = site.ID if post.ID == 0 { post.CreatedAt = time.Now() post.UpdatedAt = time.Now() if err := s.db.InsertPost(ctx, post); err != nil { return err } } else { post.UpdatedAt = time.Now() if err := s.db.UpdatePost(ctx, post); err != nil { return err } } return s.jobs.Queue(ctx, s.sb.WritePost(site, *post)) } func (s *Service) fetchLinkedPage(ctx context.Context, req NewLinkPost) (fl fetchedLink, err error) { client := resty.New() defer client.Close() // Fetch the linked site res, err := client.R().WithContext(ctx).Get(req.LinkURL) if err != nil { return fetchedLink{}, err } else if res.StatusCode() != 200 { return fetchedLink{}, fault.Newf("page returns non-200 status code %d", res.StatusCode()) } if len(res.RedirectHistory()) > 0 { fl.LinkURL = res.RedirectHistory()[len(res.RedirectHistory())-1].URL } else { fl.LinkURL = req.LinkURL } resDom, err := goquery.NewDocumentFromReader(bytes.NewReader(res.Bytes())) if err != nil { return fetchedLink{}, err } fl.LinkTitle = resDom.Find("title").Text() // Fetch the via site if req.ViaURL != "" { if res, err := client.R().WithContext(ctx).Get(req.ViaURL); err == nil && res.StatusCode() == 200 { if len(res.RedirectHistory()) > 0 { fl.ViaURL = res.RedirectHistory()[len(res.RedirectHistory())-1].URL } else { fl.ViaURL = req.LinkURL } if viaDom, err := goquery.NewDocumentFromReader(bytes.NewReader(res.Bytes())); err == nil { fl.ViaTitle = viaDom.Find("title").Text() } } } return fl, nil } type NewPost struct { Title string `json:"title" form:"title"` Body string `json:"body" form:"body"` Params map[string]string `json:"params" form:"params"` } type NewLinkPost struct { LinkURL string `json:"title" form:"link_url"` ViaURL string `json:"body" form:"via_url"` } type fetchedLink struct { LinkURL string LinkTitle string ViaURL string ViaTitle string } var linkedPostTemplate = template.Must(template.New("").Parse(`🔗 [{{.LinkTitle}}]({{.LinkURL}}) {{if .ViaURL}}Via: [{{.ViaTitle}}]({{.ViaURL}}){{end}}`))