repos / pico

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

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
M pkg/apps/pico/cli.go
+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 	}
M pkg/db/db.go
+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)
M pkg/db/postgres/storage.go
+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 	}
M pkg/db/stub/stub.go
+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+}