repos / pico

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

commit
828bab7
parent
3764a26
author
Eric Bower
date
2024-06-16 16:02:50 +0000 UTC
feat(pgs): cli cmd `stats {project}` for project analytics
2 files changed,  +77, -1
M pgs/cli.go
+72, -0
 1@@ -6,6 +6,7 @@ import (
 2 	"log/slog"
 3 	"path/filepath"
 4 	"strings"
 5+	"time"
 6 
 7 	"github.com/charmbracelet/lipgloss"
 8 	"github.com/charmbracelet/lipgloss/table"
 9@@ -63,6 +64,10 @@ func getHelpText(styles common.Styles, userName string, width int) string {
10 			"stats",
11 			"usage statistics",
12 		},
13+		{
14+			fmt.Sprintf("stats %s", projectName),
15+			fmt.Sprintf("site analytics for `%s`", projectName),
16+		},
17 		{
18 			"ls",
19 			"lists projects",
20@@ -191,6 +196,73 @@ func (c *Cmd) help() {
21 	c.output(getHelpText(c.Styles, c.User.Name, c.Width))
22 }
23 
24+func uniqueVisitorsTbl(intervals []*db.VisitInterval) *table.Table {
25+	headers := []string{"Date", "Unique Visitors"}
26+	data := [][]string{}
27+	for _, d := range intervals {
28+		data = append(data, []string{
29+			d.Interval.Format(time.RFC3339Nano),
30+			fmt.Sprintf("%d", d.Visitors),
31+		})
32+	}
33+
34+	t := table.New().
35+		Border(lipgloss.RoundedBorder()).
36+		Headers(headers...).
37+		Rows(data...)
38+	return t
39+}
40+
41+func visitUrlsTbl(urls []*db.VisitUrl) *table.Table {
42+	headers := []string{"Site", "Count"}
43+	data := [][]string{}
44+	for _, d := range urls {
45+		data = append(data, []string{
46+			d.Url,
47+			fmt.Sprintf("%d", d.Count),
48+		})
49+	}
50+
51+	t := table.New().
52+		Border(lipgloss.RoundedBorder()).
53+		Headers(headers...).
54+		Rows(data...)
55+	return t
56+}
57+
58+func (c *Cmd) statsByProject(projectName string) error {
59+	project, err := c.Dbpool.FindProjectByName(c.User.ID, projectName)
60+	if err != nil {
61+		return errors.Join(err, fmt.Errorf("project (%s) does not exit", projectName))
62+	}
63+
64+	opts := &db.SummaryOpts{
65+		FkID:     project.ID,
66+		By:       "project_id",
67+		Interval: "day",
68+		Origin:   shared.StartOfMonth(),
69+	}
70+
71+	summary, err := c.Dbpool.VisitSummary(opts)
72+	if err != nil {
73+		return err
74+	}
75+
76+	c.output("Top URLs")
77+	topUrlsTbl := visitUrlsTbl(summary.TopUrls)
78+	c.output(topUrlsTbl.Width(c.Width).String())
79+
80+	c.output("Top Referers")
81+	topRefsTbl := visitUrlsTbl(summary.TopReferers)
82+	c.output(topRefsTbl.Width(c.Width).String())
83+
84+	uniqueTbl := uniqueVisitorsTbl(summary.Intervals)
85+	c.output("Unique Visitors this Month")
86+	c.output(uniqueTbl.Width(c.Width).String())
87+
88+	return nil
89+}
90+
91 func (c *Cmd) stats(cfgMaxSize uint64) error {
92 	ff, err := c.Dbpool.FindFeatureForUser(c.User.ID, "plus")
93 	if err != nil {
M pgs/wish.go
+5, -1
 1@@ -136,7 +136,11 @@ func WishMiddleware(handler *uploadassets.UploadAssetHandler) wish.Middleware {
 2 				"cmdArgs", cmdArgs,
 3 			)
 4 
 5-			if cmd == "link" {
 6+			if cmd == "stats" {
 7+				err := opts.statsByProject(projectName)
 8+				opts.bail(err)
 9+				return
10+			} else if cmd == "link" {
11 				linkCmd, write := flagSet("link", sesh)
12 				linkTo := linkCmd.String("to", "", "symbolic link to this project")
13 				if !flagCheck(linkCmd, projectName, cmdArgs) {