From 5b5515c8bf36a823e37399c02417e928350c1022 Mon Sep 17 00:00:00 2001 From: Aram Petrosyan Date: Tue, 31 Mar 2026 14:00:28 +0100 Subject: [PATCH] feat: Add app version logging to handshake --- internal/api/handshake.go | 2 ++ internal/api/service.go | 16 +++++++++++++++- internal/api/types.go | 12 ++++++++++++ internal/identity/auth.go | 2 ++ internal/settings/service.go | 1 + main.go | 7 +++++-- 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/internal/api/handshake.go b/internal/api/handshake.go index 53d0d18..ae4196a 100644 --- a/internal/api/handshake.go +++ b/internal/api/handshake.go @@ -11,6 +11,7 @@ import ( "time" "connectrpc.com/connect" + "github.com/spf13/viper" "github.com/zalando/go-keyring" apiv1 "github.com/focusd-so/focusd/gen/api/v1" @@ -44,6 +45,7 @@ func PerformHandshake(ctx context.Context, client apiv1connect.ApiServiceClient) req := connect.NewRequest(&apiv1.DeviceHandshakeRequest{ DeviceFingerprint: deviceFingerPrint, + AppVersion: viper.GetString("app_version"), }) req.Header().Set("X-Timestamp", timestamp) diff --git a/internal/api/service.go b/internal/api/service.go index 93bf0f8..c300ca5 100644 --- a/internal/api/service.go +++ b/internal/api/service.go @@ -14,6 +14,7 @@ import ( "connectrpc.com/connect" "gorm.io/gorm" + "gorm.io/gorm/clause" apiv1 "github.com/focusd-so/focusd/gen/api/v1" "github.com/focusd-so/focusd/gen/api/v1/apiv1connect" @@ -26,7 +27,7 @@ type ServiceImpl struct { } func NewServiceImpl(gormDB *gorm.DB, productIDs map[apiv1.CheckoutProduct]string) (*ServiceImpl, error) { - if err := gormDB.AutoMigrate(&User{}, &UserDevice{}, &HandshakeNonce{}, &LLMProxyUsage{}); err != nil { + if err := gormDB.AutoMigrate(&User{}, &UserDevice{}, &HandshakeNonce{}, &LLMProxyUsage{}, &AppVersionLog{}); err != nil { return nil, fmt.Errorf("failed to migrate database: %w", err) } @@ -57,6 +58,19 @@ func (s *ServiceImpl) DeviceHandshake(ctx context.Context, req *connect.Request[ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("db error: %w", err)) } + if req.Msg.AppVersion != "" { + now := time.Now().Unix() + s.gormDB.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "user_id"}, {Name: "device_fingerprint"}, {Name: "version"}}, + DoUpdates: clause.AssignmentColumns([]string{"timestamp"}), + }).Create(&AppVersionLog{ + UserID: user.ID, + DeviceFingerprint: req.Msg.DeviceFingerprint, + Version: req.Msg.AppVersion, + Timestamp: now, + }) + } + sessionToken, err := MintToken(user, user.Role) if err != nil { return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to mint session: %w", err)) diff --git a/internal/api/types.go b/internal/api/types.go index dfcb250..396fb74 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -62,3 +62,15 @@ type LLMProxyUsage struct { func (l *LLMProxyUsage) TableName() string { return "llm_proxy_usage" } + +type AppVersionLog struct { + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` + UserID int64 `gorm:"not null;uniqueIndex:idx_user_device_version" json:"user_id"` + DeviceFingerprint string `gorm:"not null;uniqueIndex:idx_user_device_version" json:"device_fingerprint"` + Version string `gorm:"not null;uniqueIndex:idx_user_device_version" json:"version"` + Timestamp int64 `gorm:"not null" json:"timestamp"` +} + +func (a *AppVersionLog) TableName() string { + return "app_version_log" +} diff --git a/internal/identity/auth.go b/internal/identity/auth.go index d03eec0..1f19822 100644 --- a/internal/identity/auth.go +++ b/internal/identity/auth.go @@ -14,6 +14,7 @@ import ( apiv1 "github.com/focusd-so/focusd/gen/api/v1" "github.com/focusd-so/focusd/gen/api/v1/apiv1connect" "github.com/focusd-so/focusd/internal/native" + "github.com/spf13/viper" ) var ( @@ -81,6 +82,7 @@ func PerformHandshake(ctx context.Context, client apiv1connect.ApiServiceClient) req := connect.NewRequest(&apiv1.DeviceHandshakeRequest{ DeviceFingerprint: deviceFingerPrint, + AppVersion: viper.GetString("app_version"), }) req.Header().Set("X-Timestamp", timestamp) diff --git a/internal/settings/service.go b/internal/settings/service.go index c7d4795..3cfec8c 100644 --- a/internal/settings/service.go +++ b/internal/settings/service.go @@ -19,6 +19,7 @@ func NewService(configDir string) (*Service, error) { viper.SetDefault("history_retention_days", 30) viper.SetDefault("distraction_allowance_minutes", 60) viper.SetDefault("custom_rules_js", []string{}) + viper.SetDefault("app_version", "dev") // Config file settings viper.SetConfigName("config") diff --git a/main.go b/main.go index ceaf4bc..1a46103 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/gorilla/websocket" + "github.com/spf13/viper" _ "github.com/tursodatabase/libsql-client-go/libsql" "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events" @@ -29,12 +30,12 @@ import ( "gorm.io/gorm" "github.com/focusd-so/focusd/internal/api" - "github.com/focusd-so/focusd/internal/fs" - "github.com/focusd-so/focusd/internal/sandbox" "github.com/focusd-so/focusd/internal/extension" + "github.com/focusd-so/focusd/internal/fs" "github.com/focusd-so/focusd/internal/identity" "github.com/focusd-so/focusd/internal/native" "github.com/focusd-so/focusd/internal/nativemessaging" + "github.com/focusd-so/focusd/internal/sandbox" "github.com/focusd-so/focusd/internal/settings" "github.com/focusd-so/focusd/internal/updater" "github.com/focusd-so/focusd/internal/usage" @@ -97,6 +98,8 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + viper.Set("app_version", Version) + extensionSessionAPIKey, err := generateSessionAPIKey() if err != nil { log.Fatalf("failed to generate extension session API key: %v", err)