feat: expose active user counts in API

This commit is contained in:
Sam 2023-05-06 15:59:52 +02:00
parent e8d9ccb1ac
commit de460720da
No known key found for this signature in database
GPG Key ID: B4EF20DDE721CAA1
4 changed files with 79 additions and 12 deletions

View File

@ -45,7 +45,35 @@ func (db *DB) initMetrics() (err error) {
Name: "pronouns_users_active", Name: "pronouns_users_active",
Help: "The number of users active in the past 30 days", Help: "The number of users active in the past 30 days",
}, func() float64 { }, func() float64 {
count, err := db.ActiveUsers(context.Background()) count, err := db.ActiveUsers(context.Background(), ActiveMonth)
if err != nil {
log.Errorf("getting active user count for metrics: %v", err)
}
return float64(count)
}))
if err != nil {
return errors.Wrap(err, "registering active user count gauge")
}
err = prometheus.Register(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: "pronouns_users_active_week",
Help: "The number of users active in the past 7 days",
}, func() float64 {
count, err := db.ActiveUsers(context.Background(), ActiveWeek)
if err != nil {
log.Errorf("getting active user count for metrics: %v", err)
}
return float64(count)
}))
if err != nil {
return errors.Wrap(err, "registering active user count gauge")
}
err = prometheus.Register(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: "pronouns_users_active_day",
Help: "The number of users active in the past 1 day",
}, func() float64 {
count, err := db.ActiveUsers(context.Background(), ActiveDay)
if err != nil { if err != nil {
log.Errorf("getting active user count for metrics: %v", err) log.Errorf("getting active user count for metrics: %v", err)
} }
@ -95,10 +123,14 @@ func (db *DB) TotalMemberCount(ctx context.Context) (numMembers int64, err error
return numMembers, nil return numMembers, nil
} }
const activeTime = 30 * 24 * time.Hour const (
ActiveMonth = 30 * 24 * time.Hour
ActiveWeek = 7 * 24 * time.Hour
ActiveDay = 24 * time.Hour
)
func (db *DB) ActiveUsers(ctx context.Context) (numUsers int64, err error) { func (db *DB) ActiveUsers(ctx context.Context, dur time.Duration) (numUsers int64, err error) {
t := time.Now().Add(-activeTime) t := time.Now().Add(-dur)
err = db.QueryRow(ctx, "SELECT COUNT(*) FROM users WHERE deleted_at IS NULL AND last_active > $1", t).Scan(&numUsers) err = db.QueryRow(ctx, "SELECT COUNT(*) FROM users WHERE deleted_at IS NULL AND last_active > $1", t).Scan(&numUsers)
if err != nil { if err != nil {
return 0, errors.Wrap(err, "querying active user count") return 0, errors.Wrap(err, "querying active user count")

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"os" "os"
"codeberg.org/u1f320/pronouns.cc/backend/db"
"codeberg.org/u1f320/pronouns.cc/backend/server" "codeberg.org/u1f320/pronouns.cc/backend/server"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -21,11 +22,18 @@ func Mount(srv *server.Server, r chi.Router) {
} }
type MetaResponse struct { type MetaResponse struct {
GitRepository string `json:"git_repository"` GitRepository string `json:"git_repository"`
GitCommit string `json:"git_commit"` GitCommit string `json:"git_commit"`
Users int64 `json:"users"` Users MetaUsers `json:"users"`
Members int64 `json:"members"` Members int64 `json:"members"`
RequireInvite bool `json:"require_invite"` RequireInvite bool `json:"require_invite"`
}
type MetaUsers struct {
Total int64 `json:"total"`
ActiveMonth int64 `json:"active_month"`
ActiveWeek int64 `json:"active_week"`
ActiveDay int64 `json:"active_day"`
} }
func (s *Server) meta(w http.ResponseWriter, r *http.Request) error { func (s *Server) meta(w http.ResponseWriter, r *http.Request) error {
@ -36,6 +44,21 @@ func (s *Server) meta(w http.ResponseWriter, r *http.Request) error {
return errors.Wrap(err, "querying user count") return errors.Wrap(err, "querying user count")
} }
activeMonth, err := s.DB.ActiveUsers(ctx, db.ActiveMonth)
if err != nil {
return errors.Wrap(err, "querying user count")
}
activeWeek, err := s.DB.ActiveUsers(ctx, db.ActiveWeek)
if err != nil {
return errors.Wrap(err, "querying user count")
}
activeDay, err := s.DB.ActiveUsers(ctx, db.ActiveDay)
if err != nil {
return errors.Wrap(err, "querying user count")
}
numMembers, err := s.DB.TotalMemberCount(ctx) numMembers, err := s.DB.TotalMemberCount(ctx)
if err != nil { if err != nil {
return errors.Wrap(err, "querying user count") return errors.Wrap(err, "querying user count")
@ -44,7 +67,12 @@ func (s *Server) meta(w http.ResponseWriter, r *http.Request) error {
render.JSON(w, r, MetaResponse{ render.JSON(w, r, MetaResponse{
GitRepository: server.Repository, GitRepository: server.Repository,
GitCommit: server.Revision, GitCommit: server.Revision,
Users: numUsers, Users: MetaUsers{
Total: numUsers,
ActiveMonth: activeMonth,
ActiveWeek: activeWeek,
ActiveDay: activeDay,
},
Members: numMembers, Members: numMembers,
RequireInvite: os.Getenv("REQUIRE_INVITE") == "true", RequireInvite: os.Getenv("REQUIRE_INVITE") == "true",
}) })

View File

@ -8,11 +8,18 @@ export interface SignupResponse {
export interface MetaResponse { export interface MetaResponse {
git_repository: string; git_repository: string;
git_commit: string; git_commit: string;
users: number; users: MetaUsers;
members: number; members: number;
require_invite: boolean; require_invite: boolean;
} }
export interface MetaUsers {
total: number;
active_month: number;
active_week: number;
active_day: number;
}
export interface UrlsResponse { export interface UrlsResponse {
discord?: string; discord?: string;
tumblr?: string; tumblr?: string;

View File

@ -61,7 +61,7 @@
<a href="/page/privacy">Privacy policy</a> <a href="/page/privacy">Privacy policy</a>
</p> </p>
<p class="ms-auto"> <p class="ms-auto">
Users: <strong>{data.users}</strong> &middot; Members: <strong>{data.members}</strong> Users: <strong>{data.users.total}</strong> &middot; Members: <strong>{data.members}</strong>
</p> </p>
</div> </div>
</footer> </footer>