feat: expose active user counts in API
This commit is contained in:
		
							parent
							
								
									e8d9ccb1ac
								
							
						
					
					
						commit
						de460720da
					
				
					 4 changed files with 79 additions and 12 deletions
				
			
		|  | @ -45,7 +45,35 @@ func (db *DB) initMetrics() (err error) { | |||
| 		Name: "pronouns_users_active", | ||||
| 		Help: "The number of users active in the past 30 days", | ||||
| 	}, 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 { | ||||
| 			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 | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
| 	t := time.Now().Add(-activeTime) | ||||
| func (db *DB) ActiveUsers(ctx context.Context, dur time.Duration) (numUsers int64, err error) { | ||||
| 	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) | ||||
| 	if err != nil { | ||||
| 		return 0, errors.Wrap(err, "querying active user count") | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import ( | |||
| 	"net/http" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"codeberg.org/u1f320/pronouns.cc/backend/db" | ||||
| 	"codeberg.org/u1f320/pronouns.cc/backend/server" | ||||
| 	"emperror.dev/errors" | ||||
| 	"github.com/go-chi/chi/v5" | ||||
|  | @ -23,11 +24,18 @@ func Mount(srv *server.Server, r chi.Router) { | |||
| type MetaResponse struct { | ||||
| 	GitRepository string    `json:"git_repository"` | ||||
| 	GitCommit     string    `json:"git_commit"` | ||||
| 	Users         int64  `json:"users"` | ||||
| 	Users         MetaUsers `json:"users"` | ||||
| 	Members       int64     `json:"members"` | ||||
| 	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 { | ||||
| 	ctx := r.Context() | ||||
| 
 | ||||
|  | @ -36,6 +44,21 @@ func (s *Server) meta(w http.ResponseWriter, r *http.Request) error { | |||
| 		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) | ||||
| 	if err != nil { | ||||
| 		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{ | ||||
| 		GitRepository: server.Repository, | ||||
| 		GitCommit:     server.Revision, | ||||
| 		Users:         numUsers, | ||||
| 		Users: MetaUsers{ | ||||
| 			Total:       numUsers, | ||||
| 			ActiveMonth: activeMonth, | ||||
| 			ActiveWeek:  activeWeek, | ||||
| 			ActiveDay:   activeDay, | ||||
| 		}, | ||||
| 		Members:       numMembers, | ||||
| 		RequireInvite: os.Getenv("REQUIRE_INVITE") == "true", | ||||
| 	}) | ||||
|  |  | |||
|  | @ -8,11 +8,18 @@ export interface SignupResponse { | |||
| export interface MetaResponse { | ||||
|   git_repository: string; | ||||
|   git_commit: string; | ||||
|   users: number; | ||||
|   users: MetaUsers; | ||||
|   members: number; | ||||
|   require_invite: boolean; | ||||
| } | ||||
| 
 | ||||
| export interface MetaUsers { | ||||
|   total: number; | ||||
|   active_month: number; | ||||
|   active_week: number; | ||||
|   active_day: number; | ||||
| } | ||||
| 
 | ||||
| export interface UrlsResponse { | ||||
|   discord?: string; | ||||
|   tumblr?: string; | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ | |||
|         <a href="/page/privacy">Privacy policy</a> | ||||
|       </p> | ||||
|       <p class="ms-auto"> | ||||
|         Users: <strong>{data.users}</strong> · Members: <strong>{data.members}</strong> | ||||
|         Users: <strong>{data.users.total}</strong> · Members: <strong>{data.members}</strong> | ||||
|       </p> | ||||
|     </div> | ||||
|   </footer> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue