repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

pico / prose
Eric Bower · 16 Jun 24

cli.go

  1package prose
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"log/slog"
  7	"strings"
  8
  9	"github.com/charmbracelet/ssh"
 10	"github.com/charmbracelet/wish"
 11	bm "github.com/charmbracelet/wish/bubbletea"
 12	"github.com/picosh/pico/db"
 13	"github.com/picosh/pico/shared"
 14	"github.com/picosh/pico/tui/common"
 15)
 16
 17func getUser(s ssh.Session, dbpool db.DB) (*db.User, error) {
 18	var err error
 19	key, err := shared.KeyText(s)
 20	if err != nil {
 21		return nil, fmt.Errorf("key not found")
 22	}
 23
 24	user, err := dbpool.FindUserForKey(s.User(), key)
 25	if err != nil {
 26		return nil, err
 27	}
 28
 29	if user.Name == "" {
 30		return nil, fmt.Errorf("must have username set")
 31	}
 32
 33	return user, nil
 34}
 35
 36type Cmd struct {
 37	User    *db.User
 38	Session shared.CmdSession
 39	Log     *slog.Logger
 40	Dbpool  db.DB
 41	Styles  common.Styles
 42	Width   int
 43	Height  int
 44}
 45
 46func (c *Cmd) output(out string) {
 47	_, _ = c.Session.Write([]byte(out + "\r\n"))
 48}
 49
 50func (c *Cmd) help() {
 51	helpStr := "Commands: [help, stats]\n"
 52	c.output(helpStr)
 53}
 54
 55func (c *Cmd) statsByPost(postSlug string) error {
 56	post, err := c.Dbpool.FindPostWithSlug(postSlug, c.User.ID, "prose")
 57	if err != nil {
 58		return errors.Join(err, fmt.Errorf("post (%s) does not exit", postSlug))
 59	}
 60
 61	opts := &db.SummaryOpts{
 62		FkID:     post.ID,
 63		By:       "post_id",
 64		Interval: "day",
 65		Origin:   shared.StartOfMonth(),
 66	}
 67
 68	summary, err := c.Dbpool.VisitSummary(opts)
 69	if err != nil {
 70		return err
 71	}
 72
 73	c.output("Top URLs")
 74	topUrlsTbl := common.VisitUrlsTbl(summary.TopUrls)
 75	c.output(topUrlsTbl.Width(c.Width).String())
 76
 77	c.output("Top Referers")
 78	topRefsTbl := common.VisitUrlsTbl(summary.TopReferers)
 79	c.output(topRefsTbl.Width(c.Width).String())
 80
 81	uniqueTbl := common.UniqueVisitorsTbl(summary.Intervals)
 82	c.output("Unique Visitors this Month")
 83	c.output(uniqueTbl.Width(c.Width).String())
 84
 85	return nil
 86}
 87
 88func (c *Cmd) stats() error {
 89	opts := &db.SummaryOpts{
 90		FkID:     c.User.ID,
 91		By:       "user_id",
 92		Interval: "day",
 93		Origin:   shared.StartOfMonth(),
 94		Where:    "AND (post_id IS NOT NULL OR (post_id IS NULL AND project_id IS NULL))",
 95	}
 96
 97	summary, err := c.Dbpool.VisitSummary(opts)
 98	if err != nil {
 99		return err
100	}
101
102	c.output("Top URLs")
103	topUrlsTbl := common.VisitUrlsTbl(summary.TopUrls)
104	c.output(topUrlsTbl.Width(c.Width).String())
105
106	c.output("Top Referers")
107	topRefsTbl := common.VisitUrlsTbl(summary.TopReferers)
108	c.output(topRefsTbl.Width(c.Width).String())
109
110	uniqueTbl := common.UniqueVisitorsTbl(summary.Intervals)
111	c.output("Unique Visitors this Month")
112	c.output(uniqueTbl.Width(c.Width).String())
113
114	return nil
115}
116
117type CliHandler struct {
118	DBPool db.DB
119	Logger *slog.Logger
120}
121
122func WishMiddleware(handler *CliHandler) wish.Middleware {
123	dbpool := handler.DBPool
124	log := handler.Logger
125
126	return func(next ssh.Handler) ssh.Handler {
127		return func(sesh ssh.Session) {
128			args := sesh.Command()
129			if len(args) == 0 {
130				next(sesh)
131				return
132			}
133
134			// default width and height when no pty
135			width := 80
136			height := 24
137			pty, _, ok := sesh.Pty()
138			if ok {
139				width = pty.Window.Width
140				height = pty.Window.Height
141			}
142
143			user, err := getUser(sesh, dbpool)
144			if err != nil {
145				wish.Errorln(sesh, err)
146				return
147			}
148
149			renderer := bm.MakeRenderer(sesh)
150			styles := common.DefaultStyles(renderer)
151
152			opts := Cmd{
153				Session: sesh,
154				User:    user,
155				Log:     log,
156				Dbpool:  dbpool,
157				Styles:  styles,
158				Width:   width,
159				Height:  height,
160			}
161
162			cmd := strings.TrimSpace(args[0])
163			if len(args) == 1 {
164				if cmd == "help" {
165					opts.help()
166					return
167				} else if cmd == "stats" {
168					err := opts.stats()
169					if err != nil {
170						wish.Fatalln(sesh, err)
171					}
172					return
173				} else {
174					next(sesh)
175					return
176				}
177			}
178
179			postSlug := strings.TrimSpace(args[1])
180			cmdArgs := args[2:]
181			log.Info(
182				"pgs middleware detected command",
183				"args", args,
184				"cmd", cmd,
185				"post", postSlug,
186				"cmdArgs", cmdArgs,
187			)
188
189			if cmd == "stats" {
190				err := opts.statsByPost(postSlug)
191				if err != nil {
192					wish.Fatalln(sesh, err)
193				}
194				return
195			} else {
196				next(sesh)
197				return
198			}
199		}
200	}
201}