repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / apps / feeds
Eric Bower  ·  2025-09-07

cli.go

  1package feeds
  2
  3import (
  4	"fmt"
  5	"text/tabwriter"
  6	"time"
  7
  8	"github.com/adhocore/gronx"
  9	"github.com/picosh/pico/pkg/db"
 10	"github.com/picosh/pico/pkg/pssh"
 11	"github.com/picosh/pico/pkg/shared"
 12)
 13
 14func Middleware(dbpool db.DB, cfg *shared.ConfigSite) pssh.SSHServerMiddleware {
 15	return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
 16		return func(sesh *pssh.SSHServerConnSession) error {
 17			args := sesh.Command()
 18
 19			logger := pssh.GetLogger(sesh)
 20			user := pssh.GetUser(sesh)
 21
 22			if user == nil {
 23				err := fmt.Errorf("user not found")
 24				_, _ = fmt.Fprintln(sesh.Stderr(), err)
 25				return err
 26			}
 27
 28			logger = shared.LoggerWithUser(logger, user)
 29
 30			cmd := "help"
 31			if len(args) > 0 {
 32				cmd = args[0]
 33			}
 34
 35			switch cmd {
 36			case "help":
 37				_, _ = fmt.Fprintf(sesh, "Commands: [help, ls, rm, print, run]\r\n\r\n")
 38				writer := tabwriter.NewWriter(sesh, 0, 0, 1, ' ', tabwriter.TabIndent)
 39				_, _ = fmt.Fprintln(writer, "Cmd\tDesc")
 40				_, _ = fmt.Fprintf(
 41					writer,
 42					"%s\t%s\r\n",
 43					"help", "this help text",
 44				)
 45				_, _ = fmt.Fprintf(
 46					writer,
 47					"%s\t%s\r\n",
 48					"ls", "list feed digest posts with metadata",
 49				)
 50				_, _ = fmt.Fprintf(
 51					writer,
 52					"%s\t%s\r\n",
 53					"rm {filename}", "removes feed digest post",
 54				)
 55				_, _ = fmt.Fprintf(
 56					writer,
 57					"%s\t%s\r\n",
 58					"print {filename}", "prints the feed digest file",
 59				)
 60				_, _ = fmt.Fprintf(
 61					writer,
 62					"%s\t%s\r\n",
 63					"run {filename}", "runs the feed digest post immediately, ignoring cron timer",
 64				)
 65				return writer.Flush()
 66			case "ls":
 67				posts, err := dbpool.FindPostsForUser(&db.Pager{Page: 0, Num: 1000}, user.ID, "feeds")
 68				if err != nil {
 69					_, _ = fmt.Fprintln(sesh.Stderr(), err)
 70					return err
 71				}
 72
 73				if len(posts.Data) == 0 {
 74					_, _ = fmt.Fprintln(sesh, "no posts found")
 75				}
 76
 77				writer := tabwriter.NewWriter(sesh, 0, 0, 1, ' ', tabwriter.TabIndent)
 78				_, _ = fmt.Fprintln(writer, "Filename\tLast Digest\tNext Digest\tCron\tFailed Attempts")
 79				for _, post := range posts.Data {
 80					parsed := shared.ListParseText(post.Text)
 81
 82					nextDigest := ""
 83					cron := parsed.Cron
 84					if parsed.DigestInterval != "" {
 85						cron = DigestIntervalToCron(parsed.DigestInterval)
 86					}
 87					nd, _ := gronx.NextTickAfter(cron, DateToMin(time.Now()), true)
 88					nextDigest = nd.Format(time.RFC3339)
 89					last := post.Data.LastDigest
 90					lastStr := "never"
 91					if last != nil {
 92						lastStr = last.Format(time.RFC3339)
 93					}
 94					_, _ = fmt.Fprintf(
 95						writer,
 96						"%s\t%s\t%s\t%s\t%d/10\r\n",
 97						post.Filename,
 98						lastStr,
 99						nextDigest,
100						cron,
101						post.Data.Attempts,
102					)
103				}
104				return writer.Flush()
105			case "rm":
106				filename := args[1]
107				_, _ = fmt.Fprintf(sesh, "removing digest post %s\r\n", filename)
108				write := false
109				if len(args) > 2 {
110					writeRaw := args[2]
111					if writeRaw == "--write" {
112						write = true
113					}
114				}
115
116				post, err := dbpool.FindPostWithFilename(filename, user.ID, "feeds")
117				if err != nil {
118					_, _ = fmt.Fprintln(sesh.Stderr(), err)
119					return err
120				}
121				logger.Info("rm cmd", "filename", filename, "write", write)
122				if write {
123					err = dbpool.RemovePosts([]string{post.ID})
124					if err != nil {
125						_, _ = fmt.Fprintln(sesh.Stderr(), err)
126					}
127				}
128				_, _ = fmt.Fprintf(sesh, "digest post removed %s\r\n", filename)
129				if !write {
130					_, _ = fmt.Fprintln(sesh, "WARNING: *must* append with `--write` for the changes to persist.")
131				}
132				return err
133			case "print":
134				if len(args) < 2 {
135					err := fmt.Errorf("must provide filename of post to run")
136					_, _ = fmt.Fprintln(sesh.Stderr(), err)
137					return err
138				}
139				filename := args[1]
140				post, err := dbpool.FindPostWithFilename(filename, user.ID, "feeds")
141				if err != nil {
142					_, _ = fmt.Fprintln(sesh.Stderr(), err)
143					return err
144				}
145				_, _ = fmt.Fprintf(sesh, "%s\n", post.Text)
146				return nil
147			case "run":
148				if len(args) < 2 {
149					err := fmt.Errorf("must provide filename of post to run")
150					_, _ = fmt.Fprintln(sesh.Stderr(), err)
151					return err
152				}
153				filename := args[1]
154				post, err := dbpool.FindPostWithFilename(filename, user.ID, "feeds")
155				if err != nil {
156					_, _ = fmt.Fprintln(sesh.Stderr(), err)
157					return err
158				}
159				_, _ = fmt.Fprintf(sesh, "running feed post: %s\r\n", filename)
160				logger.Info("run cmd", "filename", filename)
161				fetcher := NewFetcher(dbpool, cfg)
162				err = fetcher.RunPost(logger, user, post, true, time.Now().UTC())
163				if err != nil {
164					_, _ = fmt.Fprintln(sesh.Stderr(), err)
165				}
166				return err
167			}
168
169			return next(sesh)
170		}
171	}
172}