diff options
Diffstat (limited to 'backend/stats.go')
| -rw-r--r-- | backend/stats.go | 81 |
1 files changed, 79 insertions, 2 deletions
diff --git a/backend/stats.go b/backend/stats.go index 2b07221..dfdf040 100644 --- a/backend/stats.go +++ b/backend/stats.go @@ -7,7 +7,10 @@ import ( "log/slog" "net/http" "regexp" + "strconv" "strings" + + "github.com/go-chi/chi/v5" ) var trimRefererReg = regexp.MustCompile(`https?://([a-z-0-9.]+(:\d+)?)/.*`) @@ -18,7 +21,9 @@ func getDB(ctx context.Context) *sql.DB { func UpdateStats(ctx context.Context, r *http.Request) error { target := r.URL.Path - if strings.HasPrefix(target, "/assets") || strings.HasPrefix(target, "/static") { + if strings.HasPrefix(target, "/assets") || + strings.HasPrefix(target, "/static") || + strings.HasPrefix(target, "/admin") { return nil } ref := r.Header.Get("Referer") @@ -32,7 +37,7 @@ func UpdateStats(ctx context.Context, r *http.Request) error { ref = subs[1] if ref == ctx.Value(configKey).(*Config).Domain || ref == fmt.Sprintf("localhost:%d", 8000) { ref = subs[0][strings.Index(subs[0], ref)+len(ref):] - if ref == target { + if ref == target || strings.HasPrefix(ref, "/admin") || ref == "/favicon.ico" { return nil } } @@ -63,3 +68,75 @@ func UpdateStats(ctx context.Context, r *http.Request) error { _, err = db.ExecContext(ctx, "UPDATE stats SET visit = ? WHERE id = ?", nb+1, id) return err } + +type statRow struct { + Origin string + Target string + Visit uint +} + +const statPerPage = 25 + +func GetStatRows(ctx context.Context, page uint) ([]statRow, error) { + rows, err := getDB(ctx).QueryContext( + ctx, + "SELECT origin, target, visit FROM stats ORDER BY visit DESC LIMIT ? OFFSET ?", + statPerPage, (page-1)*statPerPage, + ) + if err != nil { + return nil, err + } + defer rows.Close() + statRows := make([]statRow, statPerPage) + var i uint8 + for i = 0; rows.Next(); i++ { + var stat statRow + err = rows.Scan(&stat.Origin, &stat.Target, &stat.Visit) + if err != nil { + return nil, err + } + statRows[i] = stat + } + if i == 0 { + return nil, nil + } + return statRows[:i], nil +} + +type adminData struct { + *data + Rows []statRow + PagesNumber int + CurrentPage int +} + +func HandleAdmin(r *chi.Mux) { + r.Get("/admin", func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if !ctx.Value(loginKey).(bool) { + w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + d := new(adminData) + d.data = new(data) + rawPage := r.URL.Query().Get("page") + page := 1 + var err error + if rawPage != "" { + page, err = strconv.Atoi(rawPage) + if err != nil || page < 1 { + slog.Warn("invalid page number", "rawPage", rawPage) + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + } + d.Rows, err = GetStatRows(ctx, uint(page)) + if err != nil { + panic(err) + } + d.PagesNumber = page + max(len(d.Rows)-statPerPage+1, 0) + d.CurrentPage = page + d.handleGeneric(w, r, "admin", d) + }) +} |
