mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-16 08:26:11 +00:00
feat: add daily heartbeat request for counting Pocket ID instances (#578)
This commit is contained in:
61
backend/internal/job/analytics_job.go
Normal file
61
backend/internal/job/analytics_job.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||
)
|
||||
|
||||
const heartbeatUrl = "https://analytics.pocket-id.org/heartbeat"
|
||||
|
||||
func (s *Scheduler) RegisterAnalyticsJob(ctx context.Context, appConfig *service.AppConfigService, httpClient *http.Client) error {
|
||||
jobs := &AnalyticsJob{appConfig: appConfig, httpClient: httpClient}
|
||||
return s.registerJob(ctx, "SendHeartbeat", "0 0 * * *", jobs.sendHeartbeat, true)
|
||||
}
|
||||
|
||||
type AnalyticsJob struct {
|
||||
appConfig *service.AppConfigService
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// sendHeartbeat sends a heartbeat to the analytics service
|
||||
func (j *AnalyticsJob) sendHeartbeat(ctx context.Context) error {
|
||||
// Skip if analytics are disabled or not in production environment
|
||||
if common.EnvConfig.AnalyticsDisabled || common.EnvConfig.AppEnv != "production" {
|
||||
return nil
|
||||
}
|
||||
|
||||
body := struct {
|
||||
Version string `json:"version"`
|
||||
InstanceID string `json:"instance_id"`
|
||||
}{
|
||||
Version: common.Version,
|
||||
InstanceID: j.appConfig.GetDbConfig().InstanceID.Value,
|
||||
}
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal heartbeat body: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, heartbeatUrl, bytes.NewBuffer(bodyBytes))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create heartbeat request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := j.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send heartbeat request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("heartbeat request failed with status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ func (s *Scheduler) RegisterApiKeyExpiryJob(ctx context.Context, apiKeyService *
|
||||
appConfigService: appConfigService,
|
||||
}
|
||||
|
||||
return s.registerJob(ctx, "ExpiredApiKeyEmailJob", "0 0 * * *", jobs.checkAndNotifyExpiringApiKeys)
|
||||
return s.registerJob(ctx, "ExpiredApiKeyEmailJob", "0 0 * * *", jobs.checkAndNotifyExpiringApiKeys, false)
|
||||
}
|
||||
|
||||
func (j *ApiKeyEmailJobs) checkAndNotifyExpiringApiKeys(ctx context.Context) error {
|
||||
|
||||
@@ -15,11 +15,11 @@ func (s *Scheduler) RegisterDbCleanupJobs(ctx context.Context, db *gorm.DB) erro
|
||||
jobs := &DbCleanupJobs{db: db}
|
||||
|
||||
return errors.Join(
|
||||
s.registerJob(ctx, "ClearWebauthnSessions", "0 3 * * *", jobs.clearWebauthnSessions),
|
||||
s.registerJob(ctx, "ClearOneTimeAccessTokens", "0 3 * * *", jobs.clearOneTimeAccessTokens),
|
||||
s.registerJob(ctx, "ClearOidcAuthorizationCodes", "0 3 * * *", jobs.clearOidcAuthorizationCodes),
|
||||
s.registerJob(ctx, "ClearOidcRefreshTokens", "0 3 * * *", jobs.clearOidcRefreshTokens),
|
||||
s.registerJob(ctx, "ClearAuditLogs", "0 3 * * *", jobs.clearAuditLogs),
|
||||
s.registerJob(ctx, "ClearWebauthnSessions", "0 3 * * *", jobs.clearWebauthnSessions, false),
|
||||
s.registerJob(ctx, "ClearOneTimeAccessTokens", "0 3 * * *", jobs.clearOneTimeAccessTokens, false),
|
||||
s.registerJob(ctx, "ClearOidcAuthorizationCodes", "0 3 * * *", jobs.clearOidcAuthorizationCodes, false),
|
||||
s.registerJob(ctx, "ClearOidcRefreshTokens", "0 3 * * *", jobs.clearOidcRefreshTokens, false),
|
||||
s.registerJob(ctx, "ClearAuditLogs", "0 3 * * *", jobs.clearAuditLogs, false),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
func (s *Scheduler) RegisterFileCleanupJobs(ctx context.Context, db *gorm.DB) error {
|
||||
jobs := &FileCleanupJobs{db: db}
|
||||
|
||||
return s.registerJob(ctx, "ClearUnusedDefaultProfilePictures", "0 2 * * 0", jobs.clearUnusedDefaultProfilePictures)
|
||||
return s.registerJob(ctx, "ClearUnusedDefaultProfilePictures", "0 2 * * 0", jobs.clearUnusedDefaultProfilePictures, false)
|
||||
}
|
||||
|
||||
type FileCleanupJobs struct {
|
||||
|
||||
@@ -2,8 +2,6 @@ package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||
)
|
||||
@@ -22,22 +20,7 @@ func (s *Scheduler) RegisterGeoLiteUpdateJobs(ctx context.Context, geoLiteServic
|
||||
jobs := &GeoLiteUpdateJobs{geoLiteService: geoLiteService}
|
||||
|
||||
// Register the job to run every day, at 5 minutes past midnight
|
||||
err := s.registerJob(ctx, "UpdateGeoLiteDB", "5 * */1 * *", jobs.updateGoeLiteDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run the job immediately on startup, with a 1s delay
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
err = jobs.updateGoeLiteDB(ctx)
|
||||
if err != nil {
|
||||
// Log the error only, but don't return it
|
||||
log.Printf("Failed to Update GeoLite database: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
return s.registerJob(ctx, "UpdateGeoLiteDB", "5 * */1 * *", jobs.updateGoeLiteDB, true)
|
||||
}
|
||||
|
||||
func (j *GeoLiteUpdateJobs) updateGoeLiteDB(ctx context.Context) error {
|
||||
|
||||
@@ -2,7 +2,6 @@ package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||
)
|
||||
@@ -16,19 +15,7 @@ func (s *Scheduler) RegisterLdapJobs(ctx context.Context, ldapService *service.L
|
||||
jobs := &LdapJobs{ldapService: ldapService, appConfigService: appConfigService}
|
||||
|
||||
// Register the job to run every hour
|
||||
err := s.registerJob(ctx, "SyncLdap", "0 * * * *", jobs.syncLdap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run the job immediately on startup
|
||||
err = jobs.syncLdap(ctx)
|
||||
if err != nil {
|
||||
// Log the error only, but don't return it
|
||||
log.Printf("Failed to sync LDAP: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.registerJob(ctx, "SyncLdap", "0 * * * *", jobs.syncLdap, true)
|
||||
}
|
||||
|
||||
func (j *LdapJobs) syncLdap(ctx context.Context) error {
|
||||
|
||||
@@ -43,10 +43,8 @@ func (s *Scheduler) Run(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scheduler) registerJob(ctx context.Context, name string, interval string, job func(ctx context.Context) error) error {
|
||||
_, err := s.scheduler.NewJob(
|
||||
gocron.CronJob(interval, false),
|
||||
gocron.NewTask(job),
|
||||
func (s *Scheduler) registerJob(ctx context.Context, name string, interval string, job func(ctx context.Context) error, runImmediately bool) error {
|
||||
jobOptions := []gocron.JobOption{
|
||||
gocron.WithContext(ctx),
|
||||
gocron.WithEventListeners(
|
||||
gocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
|
||||
@@ -56,6 +54,16 @@ func (s *Scheduler) registerJob(ctx context.Context, name string, interval strin
|
||||
log.Printf("Job %q failed with error: %v", name, err)
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
if runImmediately {
|
||||
jobOptions = append(jobOptions, gocron.JobOption(gocron.WithStartImmediately()))
|
||||
}
|
||||
|
||||
_, err := s.scheduler.NewJob(
|
||||
gocron.CronJob(interval, false),
|
||||
gocron.NewTask(job),
|
||||
jobOptions...,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user