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}