- commit
- 73024ce
- parent
- 5c058db
- author
- Eric Bower
- date
- 2025-06-28 09:10:59 -0400 EDT
feat(pico): `user` and `not-found` commands to cli
4 files changed,
+72,
-4
+59,
-1
1@@ -7,6 +7,7 @@ import (
2 "fmt"
3 "log/slog"
4 "strings"
5+ "time"
6
7 "github.com/picosh/pico/pkg/db"
8 "github.com/picosh/pico/pkg/pssh"
9@@ -49,10 +50,52 @@ func (c *Cmd) output(out string) {
10 }
11
12 func (c *Cmd) help() {
13- helpStr := "Commands: [help, pico+]\n"
14+ helpStr := "Commands: [help, user, logs, chat]\n"
15+ helpStr += "help - this message\n"
16+ helpStr += "user - display user information (returns name, id, account created, pico+ expiration)\n"
17+ helpStr += "logs - stream user logs\n"
18+ helpStr += "chat - IRC chat (must enable pty with `-t` to the SSH command)\n"
19+ helpStr += "not-found - return all status 404 requests for a host (hostname.com [year|month])\n"
20 c.output(helpStr)
21 }
22
23+func (c *Cmd) user() {
24+ plus := ""
25+ ff, _ := c.Dbpool.FindFeature(c.User.ID, "plus")
26+ if ff != nil {
27+ plus = ff.ExpiresAt.Format(time.RFC3339)
28+ }
29+ helpStr := fmt.Sprintf(
30+ "%s\n%s\n%s\n%s",
31+ c.User.Name,
32+ c.User.ID,
33+ c.User.CreatedAt.Format(time.RFC3339),
34+ plus,
35+ )
36+ c.output(helpStr)
37+}
38+
39+func (c *Cmd) notFound(host, interval string) error {
40+ origin := utils.StartOfYear()
41+ if interval == "month" {
42+ origin = utils.StartOfMonth()
43+ }
44+ c.output(fmt.Sprintf("starting from: %s\n", origin.Format(time.RFC3339)))
45+ urls, err := c.Dbpool.VisitUrlNotFound(&db.SummaryOpts{
46+ Host: host,
47+ UserID: c.User.ID,
48+ Limit: 100,
49+ Origin: origin,
50+ })
51+ if err != nil {
52+ return err
53+ }
54+ for _, url := range urls {
55+ c.output(fmt.Sprintf("%d %s", url.Count, url.Url))
56+ }
57+ return nil
58+}
59+
60 func (c *Cmd) logs(ctx context.Context) error {
61 conn := shared.NewPicoPipeClient()
62 stdoutPipe, err := pipeLogger.ReadLogs(ctx, c.Log, conn)
63@@ -194,6 +237,9 @@ func Middleware(handler *CliHandler) pssh.SSHServerMiddleware {
64 case "help":
65 opts.help()
66 return nil
67+ case "user":
68+ opts.user()
69+ return nil
70 case "logs":
71 err = opts.logs(sesh.Context())
72 if err != nil {
73@@ -205,6 +251,18 @@ func Middleware(handler *CliHandler) pssh.SSHServerMiddleware {
74 }
75 }
76
77+ if cmd == "not-found" {
78+ if len(args) < 3 {
79+ sesh.Fatal(fmt.Errorf("must provide host name and interval (`month` or `year`)"))
80+ return nil
81+ }
82+ err = opts.notFound(args[1], args[2])
83+ if err != nil {
84+ sesh.Fatal(err)
85+ }
86+ return nil
87+ }
88+
89 return next(sesh)
90 }
91 }
+2,
-0
1@@ -157,6 +157,7 @@ type SummaryOpts struct {
2 Host string
3 Path string
4 UserID string
5+ Limit int
6 }
7
8 type SummaryVisits struct {
9@@ -412,6 +413,7 @@ type DB interface {
10 InsertVisit(view *AnalyticsVisits) error
11 VisitSummary(opts *SummaryOpts) (*SummaryVisits, error)
12 FindVisitSiteList(opts *SummaryOpts) ([]*VisitUrl, error)
13+ VisitUrlNotFound(opts *SummaryOpts) ([]*VisitUrl, error)
14
15 AddPicoPlusUser(username, email, paymentType, txId string) error
16 FindFeature(userID string, feature string) (*FeatureFlag, error)
+7,
-3
1@@ -1097,7 +1097,11 @@ func (me *PsqlDB) visitUrl(opts *db.SummaryOpts) ([]*db.VisitUrl, error) {
2 return intervals, nil
3 }
4
5-func (me *PsqlDB) visitUrlNotFound(opts *db.SummaryOpts) ([]*db.VisitUrl, error) {
6+func (me *PsqlDB) VisitUrlNotFound(opts *db.SummaryOpts) ([]*db.VisitUrl, error) {
7+ limit := opts.Limit
8+ if limit == 0 {
9+ limit = 10
10+ }
11 where, with := visitFilterBy(opts)
12 topUrls := fmt.Sprintf(`SELECT
13 path,
14@@ -1106,7 +1110,7 @@ func (me *PsqlDB) visitUrlNotFound(opts *db.SummaryOpts) ([]*db.VisitUrl, error)
15 WHERE created_at >= $1 AND %s = $2 AND user_id = $3 AND path <> '' AND status = 404
16 GROUP BY path
17 ORDER BY path_count DESC
18- LIMIT 10`, where)
19+ LIMIT %d`, where, limit)
20
21 intervals := []*db.VisitUrl{}
22 rs, err := me.Db.Query(topUrls, opts.Origin, with, opts.UserID)
23@@ -1176,7 +1180,7 @@ func (me *PsqlDB) VisitSummary(opts *db.SummaryOpts) (*db.SummaryVisits, error)
24 return nil, err
25 }
26
27- notFound, err := me.visitUrlNotFound(opts)
28+ notFound, err := me.VisitUrlNotFound(opts)
29 if err != nil {
30 return nil, err
31 }
+4,
-0
1@@ -280,3 +280,7 @@ func (me *StubDB) FindTunsEventLogsByAddr(userID, addr string) ([]*db.TunsEventL
2 func (me *StubDB) FindTunsEventLogs(userID string) ([]*db.TunsEventLog, error) {
3 return nil, errNotImpl
4 }
5+
6+func (me *StubDB) VisitUrlNotFound(opts *db.SummaryOpts) ([]*db.VisitUrl, error) {
7+ return nil, errNotImpl
8+}